mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Align compiler and AST behavior with CPython (#8138)
* Align compiler and AST behavior with CPython
Update the compiler pipeline, AST module support, and related VM compile/eval paths toward CPython 3.14 behavior.
Use upstream Ruff parser crates directly and keep RustPython-specific syntax preflight handling in the compile path.
Refresh opcode metadata, snapshots, and targeted tests for the aligned bytecode and AST behavior.
* fix
* Fix PR 8138 CI failures
* Fix match mapping wildcard scan complexity
* fixes
* Use Ruff runtime AST fields
* Minimize residual patch diff
* refactor error handling
* Remove _ast conversion context wrappers
* Clean up _ast conversion glue and compiler warning escalation
Rename the Node conversion parameter `ctx` to `vm` throughout the _ast
module.
Remove the unused compile_program, compile_program_single,
compile_block_expression, and compile_expression forwarders; compile_top
stays as the entry point.
Stash an escalated compiler SyntaxWarning so a non-SyntaxWarning category
propagates unchanged instead of being rewritten to SyntaxError, matching
PyErr_ExceptionMatches(SyntaxWarning) in compiler_warn. Drop the now-unused
CompileWarningError::into_codegen_error.
Replace the PositionalArguments two-variant enum with a struct holding a
shared range and a PositionalArgumentsKind enum.
Assisted-by: Claude
* Represent compiler flags as a CompilerFlags bitflags
Replace the loose PY_CF_*/CO_* i32 constants in compile_mode with a
CompilerFlags bitflags covering the PyCF_* flags and __future__
CO_FUTURE_* bits. The Python-visible ast.PyCF_* attribute values are
derived from it via bits(); parser input modes stay plain consts.
builtins::compile and the compile_string flag handling test flags
through CompilerFlags::contains instead of raw bit math.
Assisted-by: Claude
* Fix PR 8138 test regressions
* Fix PR 8138 CI failures
* Avoid shell interpolation in Linux deps action
* Fix review findings in compiler/AST changes
- frame.rs: compute the MATCH_CLASS class name lazily so a successful
match no longer takes the type name lock or allocates; the
__match_args__-not-a-tuple branch reuses it instead of recomputing.
- _ast/constant.rs: add the missing leading space to the with_recursion
label ("maximum recursion depth exceeded during compilation").
- sys.set_int_max_str_digits: restore the "maxdigits must be 0 or larger
than {n}" error text.
- codegen: set CO_MAXBLOCKS to 20 in both ir.rs and compile.rs.
- _ast/exception.rs: extract the duplicated excepthandler node guard
into ensure_excepthandler_node.
- codegen: correct the validate_keywords doc comment.
- Drop redundant "CPython" qualifiers from comments added in this branch.
Assisted-by: Claude
This commit is contained in:
1 parent
473ba7391a
commit
a1ba6a662c
105 files changed
+22864
-6645
No files matched your search
@@ -66,8 +66,11 @@
|
||||
"deoptimize",
|
||||
"emscripten",
|
||||
"excs",
|
||||
"flufl",
|
||||
"fnfe",
|
||||
"fsdefault",
|
||||
"ifexp",
|
||||
"implicits",
|
||||
"interps",
|
||||
"jitted",
|
||||
"jitting",
|
||||
|
||||
@@ -39,11 +39,36 @@ runs:
|
||||
- name: Install Linux dependencies
|
||||
shell: bash
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: >
|
||||
sudo apt-get update
|
||||
env:
|
||||
GCC_MULTILIB: ${{ inputs.gcc-multilib }}
|
||||
MUSL_TOOLS: ${{ inputs.musl-tools }}
|
||||
CLANG: ${{ inputs.clang }}
|
||||
GCC_AARCH64_LINUX_GNU: ${{ inputs.gcc-aarch64-linux-gnu }}
|
||||
run: |
|
||||
if ! sudo apt-get update; then
|
||||
echo "::warning::apt-get update failed; disabling nonessential Microsoft apt sources and retrying"
|
||||
for source in /etc/apt/sources.list.d/*microsoft* /etc/apt/sources.list.d/*azure-cli*; do
|
||||
if [ -e "$source" ]; then
|
||||
sudo mv "$source" "$source.disabled"
|
||||
fi
|
||||
done
|
||||
sudo apt-get update
|
||||
fi
|
||||
|
||||
sudo apt-get install --no-install-recommends
|
||||
${{ fromJSON(inputs.gcc-multilib) && 'gcc-multilib' || '' }}
|
||||
${{ fromJSON(inputs.musl-tools) && 'musl-tools' || '' }}
|
||||
${{ fromJSON(inputs.clang) && 'clang' || '' }}
|
||||
${{ fromJSON(inputs.gcc-aarch64-linux-gnu) && 'gcc-aarch64-linux-gnu linux-libc-dev-arm64-cross libc6-dev-arm64-cross' || '' }}
|
||||
packages=()
|
||||
if [[ "$GCC_MULTILIB" == "true" ]]; then
|
||||
packages+=(gcc-multilib)
|
||||
fi
|
||||
if [[ "$MUSL_TOOLS" == "true" ]]; then
|
||||
packages+=(musl-tools)
|
||||
fi
|
||||
if [[ "$CLANG" == "true" ]]; then
|
||||
packages+=(clang)
|
||||
fi
|
||||
if [[ "$GCC_AARCH64_LINUX_GNU" == "true" ]]; then
|
||||
packages+=(gcc-aarch64-linux-gnu linux-libc-dev-arm64-cross libc6-dev-arm64-cross)
|
||||
fi
|
||||
|
||||
if ((${#packages[@]})); then
|
||||
sudo apt-get install --no-install-recommends "${packages[@]}"
|
||||
fi
|
||||
Generated
+18
-15
@@ -3631,9 +3631,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-ruff_python_ast"
|
||||
version = "0.15.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f021ff72cabf5e2cd6d8ec8813d376a8445a228dc610ab56c27bd9054cda70d4"
|
||||
version = "0.15.9"
|
||||
source = "git+https://github.com/RustPython/ruff.git?tag=0.15.19-rustpython#3c1ab2dd9987f190f3df94d28452ed5e65e106af"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bitflags 2.13.0",
|
||||
@@ -3650,9 +3649,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-ruff_python_parser"
|
||||
version = "0.15.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01e6ee78bd9671fb5766664b2695fe1f2a92a961f4d9101646c570d8acdb1e0b"
|
||||
version = "0.15.9"
|
||||
source = "git+https://github.com/RustPython/ruff.git?tag=0.15.19-rustpython#3c1ab2dd9987f190f3df94d28452ed5e65e106af"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"bstr",
|
||||
@@ -3671,9 +3669,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-ruff_python_trivia"
|
||||
version = "0.15.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79e7cfd1056f3a02ff0d2d0e4474286ca963260782f878b7b81c1dd87432e682"
|
||||
version = "0.15.9"
|
||||
source = "git+https://github.com/RustPython/ruff.git?tag=0.15.19-rustpython#3c1ab2dd9987f190f3df94d28452ed5e65e106af"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"rustpython-ruff_source_file",
|
||||
@@ -3683,9 +3680,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-ruff_source_file"
|
||||
version = "0.15.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "948107aad62ddb12a11fc7bf68a49e52a0b0a3737d415a2505e54f5a9edac737"
|
||||
version = "0.15.9"
|
||||
source = "git+https://github.com/RustPython/ruff.git?tag=0.15.19-rustpython#3c1ab2dd9987f190f3df94d28452ed5e65e106af"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"rustpython-ruff_text_size",
|
||||
@@ -3693,9 +3689,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-ruff_text_size"
|
||||
version = "0.15.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8291ee0f5a779e54ccd4e0151a0c426f8b49a123f99b5b6545db17ccdd4277aa"
|
||||
version = "0.15.9"
|
||||
source = "git+https://github.com/RustPython/ruff.git?tag=0.15.19-rustpython#3c1ab2dd9987f190f3df94d28452ed5e65e106af"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
]
|
||||
@@ -3855,6 +3850,7 @@ dependencies = [
|
||||
"static_assertions",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"thin-vec",
|
||||
"thiserror",
|
||||
"timsort",
|
||||
"wasm-bindgen",
|
||||
@@ -3881,6 +3877,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"rustpython-common",
|
||||
"rustpython-pylib",
|
||||
"rustpython-ruff_text_size",
|
||||
"rustpython-stdlib",
|
||||
"rustpython-vm",
|
||||
"serde-wasm-bindgen",
|
||||
@@ -4363,6 +4360,12 @@ version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
||||
|
||||
[[package]]
|
||||
name = "thin-vec"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
|
||||
+6
-13
@@ -183,19 +183,11 @@ rustpython-sre_engine = { path = "crates/sre_engine", version = "0.5.0" }
|
||||
rustpython-wtf8 = { path = "crates/wtf8", version = "0.5.0" }
|
||||
rustpython-doc = { path = "crates/doc", version = "0.5.0" }
|
||||
|
||||
# Use RustPython-packaged Ruff crates from the published fork while keeping
|
||||
# existing crate names in the codebase.
|
||||
ruff_python_parser = { package = "rustpython-ruff_python_parser", version = "0.15.8" }
|
||||
ruff_python_ast = { package = "rustpython-ruff_python_ast", version = "0.15.8" }
|
||||
ruff_text_size = { package = "rustpython-ruff_text_size", version = "0.15.8" }
|
||||
ruff_source_file = { package = "rustpython-ruff_source_file", version = "0.15.8" }
|
||||
# To update ruff crates, comment out the above lines and uncomment the following lines to pull directly from the Ruff repository at the specified commit hash.
|
||||
# Ruff tag 0.15.8 is based on commit c2a8815842f9dc5d24ec19385eae0f1a7188b0d9
|
||||
# at the time of this capture. We use the commit hash to ensure reproducible builds.
|
||||
# ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
|
||||
# ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
|
||||
# ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
|
||||
# ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
|
||||
# Use the RustPython Ruff fork for RustPython public `_ast` metadata.
|
||||
ruff_python_parser = { package = "rustpython-ruff_python_parser", git = "https://github.com/RustPython/ruff.git", tag = "0.15.19-rustpython" }
|
||||
ruff_python_ast = { package = "rustpython-ruff_python_ast", git = "https://github.com/RustPython/ruff.git", tag = "0.15.19-rustpython" }
|
||||
ruff_text_size = { package = "rustpython-ruff_text_size", git = "https://github.com/RustPython/ruff.git", tag = "0.15.19-rustpython" }
|
||||
ruff_source_file = { package = "rustpython-ruff_source_file", git = "https://github.com/RustPython/ruff.git", tag = "0.15.19-rustpython" }
|
||||
|
||||
der = { version = "0.8", features = ["alloc", "oid", "pem", "zeroize"] }
|
||||
phf = { version = "0.13.1", default-features = false, features = ["macros"]}
|
||||
@@ -311,6 +303,7 @@ tcl-sys = { git = "https://github.com/arihant2math/tkinter.git", tag = "v0.2.0"
|
||||
textwrap = { version = "0.16.2", default-features = false }
|
||||
termios = "0.3.3"
|
||||
thiserror = "2.0"
|
||||
thin-vec = "0.2.14"
|
||||
timsort = "0.1.2"
|
||||
tk-sys = { git = "https://github.com/arihant2math/tkinter.git", tag = "v0.2.0" }
|
||||
icu_casemap = "2"
|
||||
|
||||
Vendored
-10
@@ -150,7 +150,6 @@ class AST_Tests(unittest.TestCase):
|
||||
self.assertRaises(TypeError, ast.parse, ast.Constant(42),
|
||||
optimize=optval)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags
|
||||
def test_optimization_levels__debug__(self):
|
||||
cases = [(-1, '__debug__'), (0, '__debug__'), (1, False), (2, False)]
|
||||
for (optval, expected) in cases:
|
||||
@@ -586,7 +585,6 @@ class AST_Tests(unittest.TestCase):
|
||||
compile(m, "<test>", "exec")
|
||||
self.assertIn("but got expr()", str(cm.exception))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: expected str for name
|
||||
def test_invalid_identifier(self):
|
||||
m = ast.Module([ast.Expr(ast.Name(42, ast.Load()))], [])
|
||||
ast.fix_missing_locations(m)
|
||||
@@ -1365,7 +1363,6 @@ class CopyTests(unittest.TestCase):
|
||||
self.assertIs(repl.ctx, context)
|
||||
self.assertRaises(AttributeError, getattr, repl, 'extra')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "Name\.__replace__\ missing\ 1\ keyword\ argument:\ 'id'\." does not match "replace() does not support Name objects"
|
||||
def test_replace_reject_missing_field(self):
|
||||
# case: warn if deleted field is not replaced
|
||||
node = ast.parse('x').body[0].value
|
||||
@@ -1700,7 +1697,6 @@ Module(
|
||||
full="Module(body=[Import(names=[alias(name='_ast', asname='ast')]), ImportFrom(module='module', names=[alias(name='sub')], level=0)], type_ignores=[])",
|
||||
)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; ? ^^^^^^^^^ ^^^^^^^^^
|
||||
def test_copy_location(self):
|
||||
src = ast.parse('1 + 1', mode='eval')
|
||||
src.body.right = ast.copy_location(ast.Constant(2), src.body.right)
|
||||
@@ -1737,7 +1733,6 @@ Module(
|
||||
"end_col_offset=0), lineno=1, col_offset=0, end_lineno=1, end_col_offset=0)])"
|
||||
)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; ? ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
def test_increment_lineno(self):
|
||||
src = ast.parse('1 + 1', mode='eval')
|
||||
self.assertEqual(ast.increment_lineno(src, n=3), src)
|
||||
@@ -1959,7 +1954,6 @@ Module(
|
||||
(\
|
||||
\ ''')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: required field "lineno" missing from alias
|
||||
def test_bad_integer(self):
|
||||
# issue13436: Bad error message with invalid numeric values
|
||||
body = [ast.ImportFrom(module='time',
|
||||
@@ -3259,7 +3253,6 @@ class ASTConstructorTests(unittest.TestCase):
|
||||
r"MyAttrs.__init__ got an unexpected keyword argument 'c'."):
|
||||
obj = MyAttrs(c=3)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_fields_and_types_no_default(self):
|
||||
class FieldsAndTypesNoDefault(ast.AST):
|
||||
_fields = ('a',)
|
||||
@@ -3273,7 +3266,6 @@ class ASTConstructorTests(unittest.TestCase):
|
||||
obj = FieldsAndTypesNoDefault(a=1)
|
||||
self.assertEqual(obj.a, 1)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_incomplete_field_types(self):
|
||||
class MoreFieldsThanTypes(ast.AST):
|
||||
_fields = ('a', 'b')
|
||||
@@ -3293,7 +3285,6 @@ class ASTConstructorTests(unittest.TestCase):
|
||||
self.assertEqual(obj.a, 1)
|
||||
self.assertEqual(obj.b, 2)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Expected type 'str' but 'bytes' found.
|
||||
def test_malformed_fields_with_bytes(self):
|
||||
class BadFields(ast.AST):
|
||||
_fields = (b'\xff'*64,)
|
||||
@@ -3713,7 +3704,6 @@ class ASTOptimizationTests(unittest.TestCase):
|
||||
f"{ast.dump(optimized_tree)}",
|
||||
)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags
|
||||
def test_folding_format(self):
|
||||
code = "'%s' % (a,)"
|
||||
|
||||
|
||||
Vendored
-1
@@ -77,7 +77,6 @@ class AuditTest(unittest.TestCase):
|
||||
def test_open(self):
|
||||
self.do_test("test_open", os_helper.TESTFN)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_cantrace(self):
|
||||
self.do_test("test_cantrace")
|
||||
|
||||
|
||||
Vendored
-3
@@ -486,7 +486,6 @@ class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase):
|
||||
msg=f"source={source} mode={mode}")
|
||||
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_compile_top_level_await(self):
|
||||
"""Test whether code with top level await can be compiled.
|
||||
|
||||
@@ -627,7 +626,6 @@ class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase):
|
||||
exec(co, glob)
|
||||
self.assertEqual(type(glob['ticker']()), AsyncGeneratorType)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <_ast.Name object at 0xb40000731e3d1360> is not an instance of <class '_ast.Constant'>
|
||||
def test_compile_ast(self):
|
||||
args = ("a*__debug__", "f.py", "exec")
|
||||
raw = compile(*args, flags = ast.PyCF_ONLY_AST).body[0]
|
||||
@@ -1020,7 +1018,6 @@ class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase):
|
||||
finally:
|
||||
sys.stdout = savestdout
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Unexpected keyword argument closure
|
||||
def test_exec_closure(self):
|
||||
def function_without_closures():
|
||||
return 3 * 5
|
||||
|
||||
Vendored
-1
@@ -645,7 +645,6 @@ class CmdLineTest(unittest.TestCase):
|
||||
self.assertNotIn("\f", text)
|
||||
self.assertIn("\n 1 + 1 = 2\n ^^^^^\n", text)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_syntaxerror_multi_line_fstring(self):
|
||||
script = 'foo = f"""{}\nfoo"""\n'
|
||||
with os_helper.temp_dir() as script_dir:
|
||||
|
||||
Vendored
-1
@@ -279,7 +279,6 @@ class CodeopTests(unittest.TestCase):
|
||||
self.assertNotEqual(compile_command("a = 1\n", "abc").co_filename,
|
||||
compile("a = 1\n", "def", 'single').co_filename)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 2
|
||||
def test_warning(self):
|
||||
# Test that the warning is only returned once.
|
||||
with warnings_helper.check_warnings(
|
||||
|
||||
Vendored
-12
@@ -209,7 +209,6 @@ class TestSpecifics(unittest.TestCase):
|
||||
self.assertEqual(eval("0o777"), 511)
|
||||
self.assertEqual(eval("-0o0000010"), -8)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised
|
||||
def test_int_literals_too_long(self):
|
||||
n = 3000
|
||||
source = f"a = 1\nb = 2\nc = {'3'*n}\nd = 4"
|
||||
@@ -283,7 +282,6 @@ class TestSpecifics(unittest.TestCase):
|
||||
self.assertRaises(SyntaxError, compile, stmt, 'tmp', 'single')
|
||||
self.assertRaises(SyntaxError, compile, stmt, 'tmp', 'exec')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised by compile
|
||||
def test_import(self):
|
||||
succeed = [
|
||||
'import sys',
|
||||
@@ -348,7 +346,6 @@ class TestSpecifics(unittest.TestCase):
|
||||
l = lambda: "this is the only const"
|
||||
self.assertEqual(l.__code__.co_consts, ("this is the only const",))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised by compile
|
||||
def test_encoding(self):
|
||||
code = b'# -*- coding: badencoding -*-\npass\n'
|
||||
self.assertRaises(SyntaxError, compile, code, 'tmp', 'exec')
|
||||
@@ -465,7 +462,6 @@ class TestSpecifics(unittest.TestCase):
|
||||
# See gh-113054
|
||||
compile('if (5 if 5 else T): 0', '<eval>', 'exec')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_condition_expression_with_redundant_comparisons_compiles(self):
|
||||
# See gh-113054, gh-114083
|
||||
exprs = [
|
||||
@@ -580,7 +576,6 @@ class TestSpecifics(unittest.TestCase):
|
||||
|
||||
compile(ast.fix_missing_locations(tree), "<file>", "exec")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <code object <module> at 0xb77555080 file "<assign>1", line 1> != <code object <module> at 0xb77554f00 file "<assign>3", line 1>
|
||||
def test_compile_ast(self):
|
||||
fname = __file__
|
||||
if fname.lower().endswith('pyc'):
|
||||
@@ -696,7 +691,6 @@ class TestSpecifics(unittest.TestCase):
|
||||
self.compile_single("class T:\n pass")
|
||||
self.compile_single("c = '''\na=1\nb=2\nc=3\n'''")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised by compile_single
|
||||
def test_bad_single_statement(self):
|
||||
self.assertInvalidSingle('1\n2')
|
||||
self.assertInvalidSingle('def f(): pass')
|
||||
@@ -708,7 +702,6 @@ class TestSpecifics(unittest.TestCase):
|
||||
self.assertInvalidSingle('x = 5 # comment\nx = 6\n')
|
||||
self.assertInvalidSingle("c = '''\nd=1\n'''\na = 1\n\nb = 2\n")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: b'source code cannot contain null bytes' not found in b'OSError: stream did not contain valid UTF-8\n'
|
||||
def test_particularly_evil_undecodable(self):
|
||||
# Issue 24022
|
||||
src = b'0000\x00\n00000000000\n\x00\n\x9e\n'
|
||||
@@ -719,7 +712,6 @@ class TestSpecifics(unittest.TestCase):
|
||||
res = script_helper.run_python_until_end(fn)[0]
|
||||
self.assertIn(b"source code cannot contain null bytes", res.err)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: b'source code cannot contain null bytes' not found in b'OSError: stream did not contain valid UTF-8\n'
|
||||
def test_yet_more_evil_still_undecodable(self):
|
||||
# Issue #25388
|
||||
src = b"#\x00\n#\xfd\n"
|
||||
@@ -756,7 +748,6 @@ class TestSpecifics(unittest.TestCase):
|
||||
# check_limit("a", " if a else a")
|
||||
# check_limit("if a: pass", "\nelif a: pass", mode="exec")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "cannot contain null" does not match "invalid syntax (<dummy>, line 1)"
|
||||
def test_null_terminated(self):
|
||||
# The source code is null-terminated internally, but bytes-like
|
||||
# objects are accepted, which could be not terminated.
|
||||
@@ -1673,7 +1664,6 @@ class TestSpecifics(unittest.TestCase):
|
||||
|
||||
self.assertRaises(NameError, ns['foo'])
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; + [3, 5, 3, 5]
|
||||
def test_compile_warnings(self):
|
||||
# Each invocation of compile() emits compiler warnings, even if they
|
||||
# have the same message and line number.
|
||||
@@ -1691,7 +1681,6 @@ class TestSpecifics(unittest.TestCase):
|
||||
|
||||
self.assertEqual([wm.lineno for wm in caught], [3, 5] * 2)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; + [5, 9]
|
||||
def test_compile_warning_in_finally(self):
|
||||
# Ensure that warnings inside finally blocks are
|
||||
# only emitted once despite the block being
|
||||
@@ -1742,7 +1731,6 @@ class TestSpecifics(unittest.TestCase):
|
||||
self.assertEqual(wm.category, SyntaxWarning)
|
||||
self.assertIn("\"is\" with 'int' literal", str(wm.message))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
@support.subTests('src', [
|
||||
textwrap.dedent("""
|
||||
def f():
|
||||
|
||||
Vendored
-1
@@ -231,7 +231,6 @@ class ExceptionTests(unittest.TestCase):
|
||||
line = line.removeprefix('\ufeff')
|
||||
self.assertIn(line, cm.exception.text)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_error_offset_continuation_characters(self):
|
||||
check = self.check
|
||||
check('"\\\n"(1 for c in I,\\\n\\', 2, 2)
|
||||
|
||||
Vendored
-5
@@ -701,7 +701,6 @@ x = (
|
||||
["f'{ {{}} }'", # dict in a set
|
||||
])
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_compile_time_concat(self):
|
||||
x = 'def'
|
||||
self.assertEqual('abc' f'## {x}ghi', 'abc## defghi')
|
||||
@@ -816,7 +815,6 @@ x = (
|
||||
s = "f'{1}' 'x' 'y'" * 1024
|
||||
self.assertEqual(eval(s), '1xy' * 1024)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_format_specifier_expressions(self):
|
||||
width = 10
|
||||
precision = 4
|
||||
@@ -947,7 +945,6 @@ x = (
|
||||
["f'{3)+(4}'",
|
||||
])
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_newlines_before_syntax_error(self):
|
||||
self.assertAllRaise(SyntaxError,
|
||||
"f-string: expecting a valid expression after '{'",
|
||||
@@ -1031,7 +1028,6 @@ x = (
|
||||
r"'\N{GREEK CAPITAL LETTER DELTA'",
|
||||
])
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_backslashes_in_expression_part(self):
|
||||
self.assertEqual(f"{(
|
||||
1 +
|
||||
@@ -1732,7 +1728,6 @@ x = (
|
||||
with self.assertRaisesRegex(ValueError, error_msg):
|
||||
f'{1:_,}'
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "f-string: expecting a valid expression after '{'" does not match "invalid syntax (?, line 1)"
|
||||
def test_syntax_error_for_starred_expressions(self):
|
||||
with self.assertRaisesRegex(SyntaxError, "can't use starred expression here"):
|
||||
compile("f'{*a}'", "?", "exec")
|
||||
|
||||
-5
@@ -81,7 +81,6 @@ class FutureTest(unittest.TestCase):
|
||||
):
|
||||
from test.test_future_stmt import test_future_multiple_features # noqa: F401
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 1 != 24
|
||||
def test_unknown_future_flag(self):
|
||||
code = """
|
||||
from __future__ import nested_scopes
|
||||
@@ -135,14 +134,12 @@ class FutureTest(unittest.TestCase):
|
||||
"""
|
||||
self.assertSyntaxError(code, offset=54)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 1 != 24
|
||||
def test_future_import_star(self):
|
||||
code = """
|
||||
from __future__ import *
|
||||
"""
|
||||
self.assertSyntaxError(code, message='future feature * is not defined', offset=24)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_future_import_braces(self):
|
||||
code = """
|
||||
from __future__ import braces
|
||||
@@ -188,7 +185,6 @@ class FutureTest(unittest.TestCase):
|
||||
out = kill_python(p)
|
||||
self.assertNotIn(b'SyntaxError: invalid syntax', out)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_future_dotted_import(self):
|
||||
with self.assertRaises(ImportError):
|
||||
exec("from .__future__ import spam")
|
||||
@@ -480,7 +476,6 @@ class AnnotationsFutureTestCase(unittest.TestCase):
|
||||
self.assertEqual(foo.__code__.co_cellvars, ())
|
||||
self.assertEqual(foo().__code__.co_freevars, ())
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised
|
||||
def test_annotations_forbidden(self):
|
||||
with self.assertRaises(SyntaxError):
|
||||
self._exec_future("test: (yield)")
|
||||
|
||||
Vendored
+1
-1
@@ -159,7 +159,7 @@ Verify that syntax error's are raised for genexps used as lvalues
|
||||
...
|
||||
SyntaxError: cannot assign to generator expression
|
||||
|
||||
>>> (y for y in (1,2)) += 10 # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
|
||||
>>> (y for y in (1,2)) += 10
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SyntaxError: 'generator expression' is an illegal expression for augmented assignment
|
||||
|
||||
Vendored
-4
@@ -28,7 +28,6 @@ class GlobalTests(unittest.TestCase):
|
||||
### Syntax error cases as covered in Python/symtable.c
|
||||
######################################################
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 12 != 5
|
||||
def test_name_param(self):
|
||||
prog_text = """\
|
||||
def fn(name_param):
|
||||
@@ -36,7 +35,6 @@ def fn(name_param):
|
||||
"""
|
||||
check_syntax_error(self, prog_text, lineno=2, offset=5)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 12 != 5
|
||||
def test_name_after_assign(self):
|
||||
prog_text = """\
|
||||
def fn():
|
||||
@@ -45,7 +43,6 @@ def fn():
|
||||
"""
|
||||
check_syntax_error(self, prog_text, lineno=3, offset=5)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 12 != 5
|
||||
def test_name_after_use(self):
|
||||
prog_text = """\
|
||||
def fn():
|
||||
@@ -54,7 +51,6 @@ def fn():
|
||||
"""
|
||||
check_syntax_error(self, prog_text, lineno=3, offset=5)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 12 != 5
|
||||
def test_name_annot(self):
|
||||
prog_text_3 = """\
|
||||
def fn():
|
||||
|
||||
Vendored
-11
@@ -114,7 +114,6 @@ class TokenTests(unittest.TestCase):
|
||||
# Sanity check: no literal begins with an underscore
|
||||
self.assertRaises(NameError, eval, "_0")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_bad_numerical_literals(self):
|
||||
check = self.check_syntax_error
|
||||
check("0b12", "invalid digit '2' in binary literal")
|
||||
@@ -137,7 +136,6 @@ class TokenTests(unittest.TestCase):
|
||||
check("1e2_", "invalid decimal literal")
|
||||
check("1e+", "invalid decimal literal")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_end_of_numerical_literals(self):
|
||||
def check(test, error=False):
|
||||
with self.subTest(expr=test):
|
||||
@@ -251,7 +249,6 @@ the \'lazy\' dog.\n\
|
||||
compile(s, "<test>", "exec")
|
||||
self.assertIn("was never closed", str(cm.exception))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised
|
||||
@skip_wasi_stack_overflow()
|
||||
def test_max_level(self):
|
||||
# Macro defined in Parser/lexer/state.h
|
||||
@@ -298,7 +295,6 @@ class GrammarTests(unittest.TestCase):
|
||||
my_lst[one()-1]: int = 5
|
||||
self.assertEqual(my_lst, [5])
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_var_annot_syntax_errors(self):
|
||||
# parser pass
|
||||
check_syntax_error(self, "def f: int")
|
||||
@@ -751,7 +747,6 @@ class GrammarTests(unittest.TestCase):
|
||||
|
||||
# Check the heuristic for print & exec covers significant cases
|
||||
# As well as placing some limits on false positives
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_former_statements_refer_to_builtins(self):
|
||||
keywords = "print", "exec"
|
||||
# Cases where we want the custom error
|
||||
@@ -1165,7 +1160,6 @@ class GrammarTests(unittest.TestCase):
|
||||
""",
|
||||
True)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_yield(self):
|
||||
# Allowed as standalone statement
|
||||
def g(): yield 1
|
||||
@@ -1205,7 +1199,6 @@ class GrammarTests(unittest.TestCase):
|
||||
# Check annotation refleak on SyntaxError
|
||||
check_syntax_error(self, "def g(a:(yield)): pass")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_yield_in_comprehensions(self):
|
||||
# Check yield in comprehensions
|
||||
def g(): [x for x in [(yield 1)]]
|
||||
@@ -1302,7 +1295,6 @@ class GrammarTests(unittest.TestCase):
|
||||
else:
|
||||
self.fail("AssertionError not raised by 'assert False'")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_assert_syntax_warnings(self):
|
||||
# Ensure that we warn users if they provide a non-zero length tuple as
|
||||
# the assertion test.
|
||||
@@ -1317,7 +1309,6 @@ class GrammarTests(unittest.TestCase):
|
||||
compile('assert x, "msg"', '<testcase>', 'exec')
|
||||
compile('assert False, "msg"', '<testcase>', 'exec')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_assert_warning_promotes_to_syntax_error(self):
|
||||
# If SyntaxWarning is configured to be an error, it actually raises a
|
||||
# SyntaxError.
|
||||
@@ -1496,7 +1487,6 @@ class GrammarTests(unittest.TestCase):
|
||||
if 1 not in (): pass
|
||||
if 1 < 1 > 1 == 1 >= 1 <= 1 != 1 in 1 not in x is x is not x: pass
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_comparison_is_literal(self):
|
||||
def check(test, msg):
|
||||
self.check_syntax_warning(test, msg)
|
||||
@@ -1526,7 +1516,6 @@ class GrammarTests(unittest.TestCase):
|
||||
compile('True is x', '<testcase>', 'exec')
|
||||
compile('... is x', '<testcase>', 'exec')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_warn_missed_comma(self):
|
||||
def check(test):
|
||||
self.check_syntax_warning(test, msg)
|
||||
|
||||
Vendored
-1
@@ -219,7 +219,6 @@ class ListComprehensionTest(unittest.TestCase):
|
||||
"""
|
||||
self._check_in_scopes(code, raises=NameError)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError: compiler_make_closure: cannot find '__conditional_annotations__' in parent vars
|
||||
def test_references___conditional_annotations___nested(self):
|
||||
code = """
|
||||
class i: [lambda: __conditional_annotations__ for x in y]
|
||||
|
||||
Vendored
-5
@@ -4,35 +4,30 @@ GLOBAL_VAR = None
|
||||
|
||||
class NamedExpressionInvalidTest(unittest.TestCase):
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message
|
||||
def test_named_expression_invalid_01(self):
|
||||
code = """x := 0"""
|
||||
|
||||
with self.assertRaisesRegex(SyntaxError, "invalid syntax"):
|
||||
exec(code, {}, {})
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message
|
||||
def test_named_expression_invalid_02(self):
|
||||
code = """x = y := 0"""
|
||||
|
||||
with self.assertRaisesRegex(SyntaxError, "invalid syntax"):
|
||||
exec(code, {}, {})
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message
|
||||
def test_named_expression_invalid_03(self):
|
||||
code = """y := f(x)"""
|
||||
|
||||
with self.assertRaisesRegex(SyntaxError, "invalid syntax"):
|
||||
exec(code, {}, {})
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message
|
||||
def test_named_expression_invalid_04(self):
|
||||
code = """y0 = y1 := f(x)"""
|
||||
|
||||
with self.assertRaisesRegex(SyntaxError, "invalid syntax"):
|
||||
exec(code, {}, {})
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message
|
||||
def test_named_expression_invalid_06(self):
|
||||
code = """((a, b) := (1, 2))"""
|
||||
|
||||
|
||||
Vendored
-14
@@ -82,7 +82,6 @@ class TestInheritance(unittest.TestCase):
|
||||
self.assertEqual(self.check_mapping_then_sequence(S3()), "seq")
|
||||
self.assertEqual(self.check_mapping_then_sequence(S4()), "seq")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_late_registration_mapping(self):
|
||||
class Parent:
|
||||
pass
|
||||
@@ -106,7 +105,6 @@ class TestInheritance(unittest.TestCase):
|
||||
self.assertEqual(self.check_mapping_then_sequence(ChildPost()), "map")
|
||||
self.assertEqual(self.check_mapping_then_sequence(GrandchildPost()), "map")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_late_registration_sequence(self):
|
||||
class Parent:
|
||||
pass
|
||||
@@ -2246,7 +2244,6 @@ class TestPatma(unittest.TestCase):
|
||||
self.assertEqual(f(None), {})
|
||||
self.assertEqual(f((1, 2)), {})
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_patma_210(self):
|
||||
def f(w):
|
||||
match w:
|
||||
@@ -2955,7 +2952,6 @@ class TestSyntaxErrors(unittest.TestCase):
|
||||
pass
|
||||
""")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_invalid_syntax_3(self):
|
||||
self.assert_syntax_error("""
|
||||
match ...:
|
||||
@@ -3075,7 +3071,6 @@ class TestSyntaxErrors(unittest.TestCase):
|
||||
pass
|
||||
""")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_patterns_may_only_match_literals_and_attribute_lookups_0(self):
|
||||
self.assert_syntax_error("""
|
||||
match ...:
|
||||
@@ -3083,7 +3078,6 @@ class TestSyntaxErrors(unittest.TestCase):
|
||||
pass
|
||||
""")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_patterns_may_only_match_literals_and_attribute_lookups_1(self):
|
||||
self.assert_syntax_error("""
|
||||
match ...:
|
||||
@@ -3126,7 +3120,6 @@ class TestSyntaxErrors(unittest.TestCase):
|
||||
pass
|
||||
""")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_real_number_wrong_ops(self):
|
||||
for op in ["*", "/", "@", "**", "%", "//"]:
|
||||
with self.subTest(op=op):
|
||||
@@ -3202,7 +3195,6 @@ class TestSyntaxErrors(unittest.TestCase):
|
||||
pass
|
||||
""")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_mapping_pattern_duplicate_key_edge_case0(self):
|
||||
self.assert_syntax_error("""
|
||||
match ...:
|
||||
@@ -3210,7 +3202,6 @@ class TestSyntaxErrors(unittest.TestCase):
|
||||
pass
|
||||
""")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_mapping_pattern_duplicate_key_edge_case1(self):
|
||||
self.assert_syntax_error("""
|
||||
match ...:
|
||||
@@ -3225,8 +3216,6 @@ class TestSyntaxErrors(unittest.TestCase):
|
||||
pass
|
||||
""")
|
||||
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_mapping_pattern_duplicate_key_edge_case3(self):
|
||||
self.assert_syntax_error("""
|
||||
match ...:
|
||||
@@ -3258,7 +3247,6 @@ class TestTypeErrors(unittest.TestCase):
|
||||
self.assertEqual(x, range(10))
|
||||
self.assertIs(y, None)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_got_multiple_subpatterns_for_attribute_0(self):
|
||||
class Class:
|
||||
__match_args__ = ("a", "a")
|
||||
@@ -3273,7 +3261,6 @@ class TestTypeErrors(unittest.TestCase):
|
||||
self.assertIs(y, None)
|
||||
self.assertIs(z, None)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_got_multiple_subpatterns_for_attribute_1(self):
|
||||
class Class:
|
||||
__match_args__ = ("a",)
|
||||
@@ -3379,7 +3366,6 @@ class TestTypeErrors(unittest.TestCase):
|
||||
|
||||
class TestValueErrors(unittest.TestCase):
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_mapping_pattern_checks_duplicate_key_1(self):
|
||||
class Keys:
|
||||
KEY = "a"
|
||||
|
||||
Vendored
+3
-3
@@ -2165,7 +2165,7 @@ if not SKIP_CORO_TESTS:
|
||||
>>> def test_function():
|
||||
... asyncio.run(main(), loop_factory=asyncio.EventLoop)
|
||||
|
||||
>>> with PdbTestInput([ # TODO: RUSTPYTHON # doctest: +ELLIPSIS +EXPECTED_FAILURE
|
||||
>>> with PdbTestInput([ # doctest: +ELLIPSIS
|
||||
... 'x = await task',
|
||||
... 'p x',
|
||||
... 'x = await test()',
|
||||
@@ -2280,7 +2280,7 @@ if not SKIP_CORO_TESTS:
|
||||
>>> def test_function():
|
||||
... asyncio.run(main(), loop_factory=asyncio.EventLoop)
|
||||
|
||||
>>> with PdbTestInput([ # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
|
||||
>>> with PdbTestInput([
|
||||
... 'p var.get()',
|
||||
... 'print(await get_var())',
|
||||
... 'print(await asyncio.create_task(set_var(100)))',
|
||||
@@ -2768,7 +2768,7 @@ def test_pdb_multiline_statement():
|
||||
>>> def test_function():
|
||||
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
|
||||
|
||||
>>> with PdbTestInput([ # TODO: RUSTPYTHON # doctest: +NORMALIZE_WHITESPACE +EXPECTED_FAILURE
|
||||
>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
|
||||
... 'def f(x):',
|
||||
... ' return x * 2',
|
||||
... '',
|
||||
|
||||
Vendored
-1
@@ -157,7 +157,6 @@ class TestTranforms(BytecodeTestCase):
|
||||
self.assertNotInBytecode(code, 'UNPACK_SEQUENCE')
|
||||
self.check_lnotab(code)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; LOAD_CONST count mismatch in long-tuple branch
|
||||
def test_constant_folding_tuples_of_constants(self):
|
||||
for line, elem in (
|
||||
('a = 1,2,3', (1, 2, 3)),
|
||||
|
||||
Vendored
+1
-1
@@ -312,7 +312,7 @@ is *not* valid syntax.)
|
||||
>>> f4.__annotations__
|
||||
{'args': StarredB, 'arg1': <class 'int'>}
|
||||
|
||||
>>> def f5(*args: *b = (1,)): pass # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
|
||||
>>> def f5(*args: *b = (1,)): pass
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SyntaxError: invalid syntax
|
||||
|
||||
Vendored
-1
@@ -932,7 +932,6 @@ class PydocDocTest(unittest.TestCase):
|
||||
synopsis = pydoc.synopsis(TESTFN, {})
|
||||
self.assertEqual(synopsis, 'line 1: h\xe9')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_source_synopsis(self):
|
||||
def check(source, expected, encoding=None):
|
||||
if isinstance(source, str):
|
||||
|
||||
-7
@@ -117,7 +117,6 @@ class TestSimpleInteract(unittest.TestCase):
|
||||
SyntaxError: duplicate argument 'x' in function definition"""
|
||||
self.assertIn(r, f.getvalue())
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_runsource_shows_syntax_error_for_failed_compilation(self):
|
||||
console = InteractiveColoredConsole()
|
||||
source = "print('Hello, world!'"
|
||||
@@ -133,7 +132,6 @@ SyntaxError: duplicate argument 'x' in function definition"""
|
||||
console.runsource(source)
|
||||
mock_showsyntaxerror.assert_called_once()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_runsource_survives_null_bytes(self):
|
||||
console = InteractiveColoredConsole()
|
||||
source = "\x00\n"
|
||||
@@ -155,7 +153,6 @@ SyntaxError: duplicate argument 'x' in function definition"""
|
||||
self.assertFalse(result)
|
||||
self.assertEqual(f.getvalue(), "{'x': <class 'int'>}\n")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_future_annotations(self):
|
||||
console = InteractiveColoredConsole()
|
||||
source = dedent("""\
|
||||
@@ -210,7 +207,6 @@ class TestMoreLines(unittest.TestCase):
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertFalse(_more_lines(console, code))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_multiline_single_block(self):
|
||||
namespace = {}
|
||||
code = dedent("""\
|
||||
@@ -227,7 +223,6 @@ class TestMoreLines(unittest.TestCase):
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertFalse(_more_lines(console, code))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_multiple_statements(self):
|
||||
namespace = {}
|
||||
code = dedent("""\
|
||||
@@ -237,7 +232,6 @@ class TestMoreLines(unittest.TestCase):
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
self.assertTrue(_more_lines(console, code))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_multiple_blocks(self):
|
||||
namespace = {}
|
||||
code = dedent("""\
|
||||
@@ -285,7 +279,6 @@ class TestMoreLines(unittest.TestCase):
|
||||
|
||||
|
||||
class TestWarnings(unittest.TestCase):
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_pep_765_warning(self):
|
||||
"""
|
||||
Test that a SyntaxWarning emitted from the
|
||||
|
||||
Vendored
-10
@@ -466,7 +466,6 @@ class TestPyReplAutoindent(TestCase):
|
||||
reader = ReadlineAlikeReader(console=console, config=config)
|
||||
return reader
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_auto_indent_default(self):
|
||||
# fmt: off
|
||||
input_code = (
|
||||
@@ -486,7 +485,6 @@ class TestPyReplAutoindent(TestCase):
|
||||
output = multiline_input(reader)
|
||||
self.assertEqual(output, output_code)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_auto_indent_continuation(self):
|
||||
# auto indenting according to previous user indentation
|
||||
# fmt: off
|
||||
@@ -514,7 +512,6 @@ class TestPyReplAutoindent(TestCase):
|
||||
output = multiline_input(reader)
|
||||
self.assertEqual(output, output_code)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_auto_indent_prev_block(self):
|
||||
# auto indenting according to indentation in different block
|
||||
# fmt: off
|
||||
@@ -546,7 +543,6 @@ class TestPyReplAutoindent(TestCase):
|
||||
output2 = multiline_input(reader)
|
||||
self.assertEqual(output2, output_code)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_auto_indent_multiline(self):
|
||||
# fmt: off
|
||||
events = itertools.chain(
|
||||
@@ -586,7 +582,6 @@ class TestPyReplAutoindent(TestCase):
|
||||
output = multiline_input(reader)
|
||||
self.assertEqual(output, output_code)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_auto_indent_with_comment(self):
|
||||
# fmt: off
|
||||
events = code_to_events(
|
||||
@@ -605,7 +600,6 @@ class TestPyReplAutoindent(TestCase):
|
||||
output = multiline_input(reader)
|
||||
self.assertEqual(output, output_code)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_auto_indent_with_multicomment(self):
|
||||
# fmt: off
|
||||
events = code_to_events(
|
||||
@@ -680,7 +674,6 @@ class TestPyReplOutput(ScreenEqualMixin, TestCase):
|
||||
wrapper = _ReadlineWrapper(f_in=None, f_out=None, reader=reader)
|
||||
self.assertIs(type(wrapper.get_line_buffer()), str)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_multiline_edit(self):
|
||||
events = itertools.chain(
|
||||
code_to_events("def f():\n...\n\n"),
|
||||
@@ -744,7 +737,6 @@ class TestPyReplOutput(ScreenEqualMixin, TestCase):
|
||||
self.assertEqual(output, "1+1")
|
||||
self.assert_screen_equal(reader, "1+1", clean=True)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_history_with_multiline_entries(self):
|
||||
code = "def foo():\nx = 1\ny = 2\nz = 3\n\ndef bar():\nreturn 42\n\n"
|
||||
events = list(itertools.chain(
|
||||
@@ -1426,7 +1418,6 @@ class TestPasteEvent(TestCase):
|
||||
output = multiline_input(reader)
|
||||
self.assertEqual(output, code)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_paste_mid_newlines_not_in_paste_mode(self):
|
||||
# fmt: off
|
||||
code = (
|
||||
@@ -1448,7 +1439,6 @@ class TestPasteEvent(TestCase):
|
||||
output = multiline_input(reader)
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_paste_not_in_paste_mode(self):
|
||||
# fmt: off
|
||||
input_code = (
|
||||
|
||||
Vendored
-1
@@ -180,7 +180,6 @@ class TestReader(ScreenEqualMixin, TestCase):
|
||||
reader, _ = handle_all_events(events)
|
||||
self.assert_screen_equal(reader, "")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Lists differ
|
||||
def test_newline_within_block_trailing_whitespace(self):
|
||||
# fmt: off
|
||||
code = (
|
||||
|
||||
Vendored
-1
@@ -437,7 +437,6 @@ class TestAsyncioREPL(unittest.TestCase):
|
||||
expected = "toplevel contextvar test: ok"
|
||||
self.assertIn(expected, output, expected)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 101 != 0
|
||||
def test_toplevel_contextvars_async(self):
|
||||
user_input = dedent("""\
|
||||
from contextvars import ContextVar
|
||||
|
||||
Vendored
-1
@@ -198,7 +198,6 @@ class SymtableTest(unittest.TestCase):
|
||||
T = find_block(GenericMine, "T")
|
||||
U = find_block(GenericMine, "U")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <SymbolTableType.ANNOTATION: 'annotation'> != 'type alias'
|
||||
def test_type(self):
|
||||
self.assertEqual(self.top.get_type(), "module")
|
||||
self.assertEqual(self.Mine.get_type(), "class")
|
||||
|
||||
Vendored
+146
-161
File diff suppressed because it is too large.
Load diff
Vendored
-1
@@ -169,7 +169,6 @@ class ProfileHookTestCase(TestCaseBase):
|
||||
(1, 'return', g_ident),
|
||||
])
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_exception_propagation(self):
|
||||
def f(p):
|
||||
1/0
|
||||
|
||||
Vendored
-15
@@ -252,7 +252,6 @@ class TypeCommentTests(unittest.TestCase):
|
||||
def classic_parse(self, source):
|
||||
return ast.parse(source)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'FunctionDef' object has no attribute 'type_comment'
|
||||
def test_funcdef(self):
|
||||
for tree in self.parse_all(funcdef):
|
||||
self.assertEqual(tree.body[0].type_comment, "() -> int")
|
||||
@@ -261,7 +260,6 @@ class TypeCommentTests(unittest.TestCase):
|
||||
self.assertEqual(tree.body[0].type_comment, None)
|
||||
self.assertEqual(tree.body[1].type_comment, None)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised : feature_version=(3, 4)
|
||||
def test_asyncdef(self):
|
||||
for tree in self.parse_all(asyncdef, minver=5):
|
||||
self.assertEqual(tree.body[0].type_comment, "() -> int")
|
||||
@@ -274,12 +272,10 @@ class TypeCommentTests(unittest.TestCase):
|
||||
with self.assertRaises(SyntaxError):
|
||||
self.classic_parse(asyncvar)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised : feature_version=(3, 4)
|
||||
def test_asynccomp(self):
|
||||
for tree in self.parse_all(asynccomp, minver=6):
|
||||
pass
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised : feature_version=(3, 4)
|
||||
def test_matmul(self):
|
||||
for tree in self.parse_all(matmul, minver=5):
|
||||
pass
|
||||
@@ -288,37 +284,31 @@ class TypeCommentTests(unittest.TestCase):
|
||||
for tree in self.parse_all(fstring):
|
||||
pass
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised : feature_version=(3, 4)
|
||||
def test_underscorednumber(self):
|
||||
for tree in self.parse_all(underscorednumber, minver=6):
|
||||
pass
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised : feature_version=(3, 4)
|
||||
def test_redundantdef(self):
|
||||
for tree in self.parse_all(redundantdef, maxver=0,
|
||||
expected_regex="^Cannot have two type comments on def"):
|
||||
pass
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'FunctionDef' object has no attribute 'type_comment'
|
||||
def test_nonasciidef(self):
|
||||
for tree in self.parse_all(nonasciidef):
|
||||
self.assertEqual(tree.body[0].type_comment, "() -> àçčéñt")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'For' object has no attribute 'type_comment'
|
||||
def test_forstmt(self):
|
||||
for tree in self.parse_all(forstmt):
|
||||
self.assertEqual(tree.body[0].type_comment, "int")
|
||||
tree = self.classic_parse(forstmt)
|
||||
self.assertEqual(tree.body[0].type_comment, None)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'With' object has no attribute 'type_comment'
|
||||
def test_withstmt(self):
|
||||
for tree in self.parse_all(withstmt):
|
||||
self.assertEqual(tree.body[0].type_comment, "int")
|
||||
tree = self.classic_parse(withstmt)
|
||||
self.assertEqual(tree.body[0].type_comment, None)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'With' object has no attribute 'type_comment'
|
||||
def test_parenthesized_withstmt(self):
|
||||
for tree in self.parse_all(parenthesized_withstmt):
|
||||
self.assertEqual(tree.body[0].type_comment, "int")
|
||||
@@ -327,14 +317,12 @@ class TypeCommentTests(unittest.TestCase):
|
||||
self.assertEqual(tree.body[0].type_comment, None)
|
||||
self.assertEqual(tree.body[1].type_comment, None)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: None != 'int'
|
||||
def test_vardecl(self):
|
||||
for tree in self.parse_all(vardecl):
|
||||
self.assertEqual(tree.body[0].type_comment, "int")
|
||||
tree = self.classic_parse(vardecl)
|
||||
self.assertEqual(tree.body[0].type_comment, None)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; + (11, ' whatever')]
|
||||
def test_ignores(self):
|
||||
for tree in self.parse_all(ignores):
|
||||
self.assertEqual(
|
||||
@@ -350,7 +338,6 @@ class TypeCommentTests(unittest.TestCase):
|
||||
tree = self.classic_parse(ignores)
|
||||
self.assertEqual(tree.type_ignores, [])
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised : feature_version=(3, 4)
|
||||
def test_longargs(self):
|
||||
for tree in self.parse_all(longargs, minver=8):
|
||||
for t in tree.body:
|
||||
@@ -381,7 +368,6 @@ class TypeCommentTests(unittest.TestCase):
|
||||
self.assertIsNone(arg.type_comment, "%s(%s:%r)" %
|
||||
(t.name, arg.arg, arg.type_comment))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; Tests for inappropriately-placed type comments.
|
||||
def test_inappropriate_type_comments(self):
|
||||
"""Tests for inappropriately-placed type comments.
|
||||
|
||||
@@ -416,7 +402,6 @@ class TypeCommentTests(unittest.TestCase):
|
||||
_testcapi.Py_CompileStringExFlags(
|
||||
b"def a(f=8, #type: \x80\n\x80", "<test>", 256, flags)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: mode must be "exec", "eval", "ipython", or "single"
|
||||
def test_func_type_input(self):
|
||||
|
||||
def parse_func_type_input(source):
|
||||
|
||||
Vendored
-3
@@ -683,7 +683,6 @@ class TypeParamsClassScopeTest(unittest.TestCase):
|
||||
self.assertIs(X.foo.__type_params__[0].__bound__, float)
|
||||
self.assertIs(X.Alias.__value__, float)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; + global
|
||||
def test_binding_uses_global(self):
|
||||
ns = run_code("""
|
||||
x = "global"
|
||||
@@ -1076,7 +1075,6 @@ class TypeParamsTypeVarTest(unittest.TestCase):
|
||||
|
||||
|
||||
class TypeParamsTypeVarTupleTest(unittest.TestCase):
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "cannot use bound with TypeVarTuple" does not match "invalid syntax (<test string>, line 1)"
|
||||
def test_typevartuple_01(self):
|
||||
code = """def func1[*A: str](): pass"""
|
||||
check_syntax_error(self, code, "cannot use bound with TypeVarTuple")
|
||||
@@ -1100,7 +1098,6 @@ class TypeParamsTypeVarTupleTest(unittest.TestCase):
|
||||
|
||||
|
||||
class TypeParamsTypeVarParamSpecTest(unittest.TestCase):
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "cannot use bound with ParamSpec" does not match "invalid syntax (<test string>, line 1)"
|
||||
def test_paramspec_01(self):
|
||||
code = """def func1[**A: str](): pass"""
|
||||
check_syntax_error(self, code, "cannot use bound with ParamSpec")
|
||||
|
||||
Vendored
+4
-4
@@ -168,7 +168,7 @@ List comprehension element unpacking
|
||||
...
|
||||
SyntaxError: iterable unpacking cannot be used in comprehension
|
||||
|
||||
>>> {**{} for a in [1]} # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
|
||||
>>> {**{} for a in [1]}
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SyntaxError: dict unpacking cannot be used in dict comprehension
|
||||
@@ -356,7 +356,7 @@ Now some general starred expressions (all fail).
|
||||
...
|
||||
SyntaxError: can't use starred expression here
|
||||
|
||||
>>> (*x),y = 1, 2 # TODO: RUSTPYTHON # doctest:+ELLIPSIS +EXPECTED_FAILURE
|
||||
>>> (*x),y = 1, 2
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SyntaxError: cannot use starred expression here
|
||||
@@ -366,12 +366,12 @@ Now some general starred expressions (all fail).
|
||||
...
|
||||
SyntaxError: cannot use starred expression here
|
||||
|
||||
>>> z,(*x),y = 1, 2, 4 # TODO: RUSTPYTHON # doctest:+ELLIPSIS +EXPECTED_FAILURE
|
||||
>>> z,(*x),y = 1, 2, 4
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SyntaxError: cannot use starred expression here
|
||||
|
||||
>>> z,(*x) = 1, 2 # TODO: RUSTPYTHON # doctest:+ELLIPSIS +EXPECTED_FAILURE
|
||||
>>> z,(*x) = 1, 2
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SyntaxError: cannot use starred expression here
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
use crate::pystate::with_vm;
|
||||
use crate::unicodeobject::decode_fsdefault_and_size;
|
||||
use core::ffi::{CStr, c_char, c_int};
|
||||
use core::ptr::NonNull;
|
||||
use rustpython_vm::builtins::{PyCode, PyDict};
|
||||
use rustpython_vm::compiler::Mode;
|
||||
use rustpython_vm::function::ArgMapping;
|
||||
use rustpython_vm::scope::Scope;
|
||||
use rustpython_vm::version;
|
||||
use rustpython_vm::{AsObject, PyObject, TryFromObject};
|
||||
|
||||
const PY_SINGLE_INPUT: c_int = 256;
|
||||
const PY_FILE_INPUT: c_int = 257;
|
||||
const PY_EVAL_INPUT: c_int = 258;
|
||||
const PY_FUNC_TYPE_INPUT: c_int = 345;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn Py_CompileString(
|
||||
code: *const c_char,
|
||||
@@ -19,27 +15,11 @@ pub unsafe extern "C" fn Py_CompileString(
|
||||
start: c_int,
|
||||
) -> *mut PyObject {
|
||||
with_vm(|vm| {
|
||||
let code = unsafe { CStr::from_ptr(code) }.to_str().map_err(|_| {
|
||||
vm.new_system_error("Py_CompileString called with non UTF-8 code string")
|
||||
})?;
|
||||
let filename = unsafe { CStr::from_ptr(filename) }
|
||||
.to_str()
|
||||
.map_err(|_| vm.new_system_error("Py_CompileString called with non UTF-8 filename"))?;
|
||||
|
||||
let mode = match start {
|
||||
PY_SINGLE_INPUT => Mode::Single,
|
||||
PY_FILE_INPUT => Mode::Exec,
|
||||
PY_EVAL_INPUT => Mode::Eval,
|
||||
PY_FUNC_TYPE_INPUT => Mode::BlockExpr,
|
||||
_ => {
|
||||
return Err(
|
||||
vm.new_system_error("Invalid start argument passed to Py_CompileString")
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
vm.compile(code, mode, filename)
|
||||
.map_err(|err| vm.new_syntax_error(&err, Some(code)))
|
||||
let code = unsafe { CStr::from_ptr(code) }.to_bytes();
|
||||
let filename_size = unsafe { CStr::from_ptr(filename) }.to_bytes().len();
|
||||
let filename = decode_fsdefault_and_size(vm, filename, filename_size)?;
|
||||
let filename = filename.to_string_lossy();
|
||||
vm.compile_string_object_with_flags(code, &filename, start, 0, version::MINOR as c_int, -1)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ use core::ffi::{CStr, c_char, c_int};
|
||||
use core::ptr::NonNull;
|
||||
use core::slice;
|
||||
use core::str;
|
||||
use rustpython_vm::PyObjectRef;
|
||||
use rustpython_vm::builtins::PyStr;
|
||||
use rustpython_vm::builtins::{PyStr, PyStrRef};
|
||||
use rustpython_vm::{PyObjectRef, PyResult, VirtualMachine};
|
||||
|
||||
define_py_check!(fn PyUnicode_Check, types.str_type);
|
||||
define_py_check!(exact fn PyUnicode_CheckExact, types.str_type);
|
||||
@@ -113,26 +113,42 @@ pub unsafe extern "C" fn PyUnicode_DecodeFSDefaultAndSize(
|
||||
.try_into()
|
||||
.map_err(|_| vm.new_system_error("size must be non-negative"))?;
|
||||
|
||||
let bytes = if s.is_null() {
|
||||
if size != 0 {
|
||||
return Err(vm.new_system_error(
|
||||
"PyUnicode_DecodeFSDefaultAndSize called with null data and non-zero size",
|
||||
));
|
||||
}
|
||||
&[][..]
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts(s.cast::<u8>(), size) }
|
||||
};
|
||||
|
||||
vm.state.codec_registry.decode_text(
|
||||
vm.ctx.new_bytes(bytes.to_vec()).into(),
|
||||
vm.fs_encoding().as_str(),
|
||||
Some(vm.fs_encode_errors().to_owned()),
|
||||
vm,
|
||||
)
|
||||
decode_fsdefault_and_size(vm, s, size)
|
||||
})
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyUnicode_DecodeFSDefault(s: *const c_char) -> *mut PyObject {
|
||||
with_vm(|vm| {
|
||||
let size = unsafe { CStr::from_ptr(s) }.to_bytes().len();
|
||||
decode_fsdefault_and_size(vm, s, size)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn decode_fsdefault_and_size(
|
||||
vm: &VirtualMachine,
|
||||
s: *const c_char,
|
||||
size: usize,
|
||||
) -> PyResult<PyStrRef> {
|
||||
let bytes = if s.is_null() {
|
||||
if size != 0 {
|
||||
return Err(vm.new_system_error(
|
||||
"PyUnicode_DecodeFSDefaultAndSize called with null data and non-zero size",
|
||||
));
|
||||
}
|
||||
&[][..]
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts(s.cast::<u8>(), size) }
|
||||
};
|
||||
|
||||
vm.state.codec_registry.decode_text(
|
||||
vm.ctx.new_bytes(bytes.to_vec()).into(),
|
||||
vm.fs_encoding().as_str(),
|
||||
Some(vm.fs_encode_errors().to_owned()),
|
||||
vm,
|
||||
)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyUnicode_EncodeFSDefault(unicode: *mut PyObject) -> *mut PyObject {
|
||||
with_vm(|vm| {
|
||||
|
||||
+5723
-1831
File diff suppressed because it is too large.
Load diff
+31
-29
@@ -3,21 +3,6 @@ use core::fmt::Display;
|
||||
use rustpython_compiler_core::SourceLocation;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PatternUnreachableReason {
|
||||
NameCapture,
|
||||
Wildcard,
|
||||
}
|
||||
|
||||
impl Display for PatternUnreachableReason {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::NameCapture => write!(f, "name capture"),
|
||||
Self::Wildcard => write!(f, "wildcard"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub type CodegenError = rustpython_parser_core::source_code::LocatedError<CodegenErrorType>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -70,6 +55,8 @@ pub enum CodegenErrorType {
|
||||
SyntaxError(String),
|
||||
/// Multiple `*` detected
|
||||
MultipleStarArgs,
|
||||
MultipleStarredExpressionsInSequencePattern,
|
||||
MultipleStarredNamesInSequencePattern,
|
||||
/// Misplaced `*` expression
|
||||
InvalidStarExpr,
|
||||
/// Break statement outside of loop.
|
||||
@@ -87,14 +74,17 @@ pub enum CodegenErrorType {
|
||||
AsyncReturnValue,
|
||||
InvalidFuturePlacement,
|
||||
InvalidFutureFeature(String),
|
||||
FunctionImportStar,
|
||||
InvalidFutureBraces,
|
||||
RecursionError,
|
||||
TooManyStarUnpack,
|
||||
TooManyExpressionsInStarUnpackingSequencePattern,
|
||||
EmptyWithItems,
|
||||
EmptyWithBody,
|
||||
ForbiddenName,
|
||||
DuplicateStore(String),
|
||||
UnreachablePattern(PatternUnreachableReason),
|
||||
RepeatedAttributePattern,
|
||||
UnreachableWildcardPattern,
|
||||
UnreachableNameCapturePattern(String),
|
||||
RepeatedAttributePattern(String),
|
||||
ConflictingNameBindPattern,
|
||||
/// break/continue/return inside except* block
|
||||
BreakContinueReturnInExceptStar,
|
||||
@@ -112,6 +102,12 @@ impl fmt::Display for CodegenErrorType {
|
||||
Self::MultipleStarArgs => {
|
||||
write!(f, "multiple starred expressions in assignment")
|
||||
}
|
||||
Self::MultipleStarredExpressionsInSequencePattern => {
|
||||
write!(f, "multiple starred expressions in sequence pattern")
|
||||
}
|
||||
Self::MultipleStarredNamesInSequencePattern => {
|
||||
write!(f, "multiple starred names in sequence pattern")
|
||||
}
|
||||
Self::InvalidStarExpr => write!(f, "can't use starred expression here"),
|
||||
Self::InvalidBreak => write!(f, "'break' outside loop"),
|
||||
Self::InvalidContinue => write!(f, "'continue' not properly in loop"),
|
||||
@@ -128,9 +124,7 @@ impl fmt::Display for CodegenErrorType {
|
||||
)
|
||||
}
|
||||
Self::AsyncYieldFrom => write!(f, "'yield from' inside async function"),
|
||||
Self::AsyncReturnValue => {
|
||||
write!(f, "'return' with value inside async generator")
|
||||
}
|
||||
Self::AsyncReturnValue => write!(f, "'return' with value in async generator"),
|
||||
Self::InvalidFuturePlacement => write!(
|
||||
f,
|
||||
"from __future__ imports must occur at the beginning of the file"
|
||||
@@ -138,12 +132,16 @@ impl fmt::Display for CodegenErrorType {
|
||||
Self::InvalidFutureFeature(feat) => {
|
||||
write!(f, "future feature {feat} is not defined")
|
||||
}
|
||||
Self::FunctionImportStar => {
|
||||
write!(f, "import * only allowed at module level")
|
||||
Self::InvalidFutureBraces => write!(f, "not a chance"),
|
||||
Self::RecursionError => {
|
||||
write!(f, "maximum recursion depth exceeded during compilation")
|
||||
}
|
||||
Self::TooManyStarUnpack => {
|
||||
write!(f, "too many expressions in star-unpacking assignment")
|
||||
}
|
||||
Self::TooManyExpressionsInStarUnpackingSequencePattern => {
|
||||
write!(f, "too many expressions in star-unpacking sequence pattern")
|
||||
}
|
||||
Self::EmptyWithItems => {
|
||||
write!(f, "empty items on With")
|
||||
}
|
||||
@@ -153,14 +151,18 @@ impl fmt::Display for CodegenErrorType {
|
||||
Self::ForbiddenName => {
|
||||
write!(f, "forbidden attribute name")
|
||||
}
|
||||
Self::DuplicateStore(s) => {
|
||||
write!(f, "duplicate store {s}")
|
||||
Self::DuplicateStore(s) => write!(f, "multiple assignments to name '{s}' in pattern"),
|
||||
Self::UnreachableWildcardPattern => {
|
||||
write!(f, "wildcard makes remaining patterns unreachable")
|
||||
}
|
||||
Self::UnreachablePattern(reason) => {
|
||||
write!(f, "{reason} makes remaining patterns unreachable")
|
||||
Self::UnreachableNameCapturePattern(name) => {
|
||||
write!(
|
||||
f,
|
||||
"name capture '{name}' makes remaining patterns unreachable"
|
||||
)
|
||||
}
|
||||
Self::RepeatedAttributePattern => {
|
||||
write!(f, "attribute name repeated in class pattern")
|
||||
Self::RepeatedAttributePattern(name) => {
|
||||
write!(f, "attribute name repeated in class pattern: {name}")
|
||||
}
|
||||
Self::ConflictingNameBindPattern => {
|
||||
write!(f, "alternative patterns bind different names")
|
||||
|
||||
+335
-103
@@ -78,15 +78,108 @@ impl ConstantPool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_full(&mut self, constant: ConstantData) -> (usize, bool) {
|
||||
// CPython's _PyCode_ConstantKey() keeps NaN-bearing constants distinct
|
||||
// because Python-level NaN keys do not compare equal.
|
||||
if !Self::constant_contains_nan(&constant)
|
||||
&& let Some(idx) = self
|
||||
.constants
|
||||
fn frozenset_key_contains(elements: &[ConstantData], needle: &ConstantData) -> bool {
|
||||
if Self::constant_contains_nan(needle) {
|
||||
return false;
|
||||
}
|
||||
elements.iter().any(|element| {
|
||||
!Self::constant_contains_nan(element) && Self::constant_key_eq(element, needle)
|
||||
})
|
||||
}
|
||||
|
||||
fn frozenset_key_eq(left: &[ConstantData], right: &[ConstantData]) -> bool {
|
||||
left.iter()
|
||||
.all(|element| Self::frozenset_key_contains(right, element))
|
||||
&& right
|
||||
.iter()
|
||||
.position(|existing| existing == &constant)
|
||||
{
|
||||
.all(|element| Self::frozenset_key_contains(left, element))
|
||||
}
|
||||
|
||||
fn constant_key_eq(left: &ConstantData, right: &ConstantData) -> bool {
|
||||
match (left, right) {
|
||||
(ConstantData::Tuple { elements: left }, ConstantData::Tuple { elements: right }) => {
|
||||
left.len() == right.len()
|
||||
&& left
|
||||
.iter()
|
||||
.zip(right.iter())
|
||||
.all(|(left, right)| Self::constant_key_eq(left, right))
|
||||
}
|
||||
(
|
||||
ConstantData::Frozenset { elements: left },
|
||||
ConstantData::Frozenset { elements: right },
|
||||
) => Self::frozenset_key_eq(left, right),
|
||||
(ConstantData::Slice { elements: left }, ConstantData::Slice { elements: right }) => {
|
||||
left.iter()
|
||||
.zip(right.iter())
|
||||
.all(|(left, right)| Self::constant_key_eq(left, right))
|
||||
}
|
||||
_ => left == right,
|
||||
}
|
||||
}
|
||||
|
||||
fn canonicalize_constant_key(constant: ConstantData) -> crate::InternalResult<ConstantData> {
|
||||
match constant {
|
||||
ConstantData::Tuple { elements } => {
|
||||
let mut canonical = Vec::new();
|
||||
canonical
|
||||
.try_reserve_exact(elements.len())
|
||||
.map_err(|_| InternalError::MalformedControlFlowGraph)?;
|
||||
for element in elements {
|
||||
canonical.push(Self::canonicalize_constant_key(element)?);
|
||||
}
|
||||
Ok(ConstantData::Tuple {
|
||||
elements: canonical,
|
||||
})
|
||||
}
|
||||
ConstantData::Slice { elements } => {
|
||||
let [start, stop, step] = *elements;
|
||||
Ok(ConstantData::Slice {
|
||||
elements: Box::new([
|
||||
Self::canonicalize_constant_key(start)?,
|
||||
Self::canonicalize_constant_key(stop)?,
|
||||
Self::canonicalize_constant_key(step)?,
|
||||
]),
|
||||
})
|
||||
}
|
||||
ConstantData::Frozenset { elements } => {
|
||||
let mut canonical = Vec::new();
|
||||
canonical
|
||||
.try_reserve_exact(elements.len())
|
||||
.map_err(|_| InternalError::MalformedControlFlowGraph)?;
|
||||
for element in elements {
|
||||
let element = Self::canonicalize_constant_key(element)?;
|
||||
if !Self::frozenset_key_contains(&canonical, &element) {
|
||||
canonical.push(element);
|
||||
}
|
||||
}
|
||||
Ok(ConstantData::Frozenset {
|
||||
elements: canonical,
|
||||
})
|
||||
}
|
||||
other => Ok(other),
|
||||
}
|
||||
}
|
||||
|
||||
fn canonicalize_constant_key_infallible(constant: ConstantData) -> ConstantData {
|
||||
Self::canonicalize_constant_key(constant)
|
||||
.expect("constant key canonicalization only fails on allocation error")
|
||||
}
|
||||
|
||||
/// Index of an already-stored constant equal to `constant`, if any.
|
||||
/// _PyCode_ConstantKey() keeps NaN-bearing constants distinct because
|
||||
/// Python-level NaN keys do not compare equal.
|
||||
fn find_existing(&self, constant: &ConstantData) -> Option<usize> {
|
||||
if Self::constant_contains_nan(constant) {
|
||||
return None;
|
||||
}
|
||||
self.constants
|
||||
.iter()
|
||||
.position(|existing| Self::constant_key_eq(existing, constant))
|
||||
}
|
||||
|
||||
pub fn insert_full(&mut self, constant: ConstantData) -> (usize, bool) {
|
||||
let constant = Self::canonicalize_constant_key_infallible(constant);
|
||||
if let Some(idx) = self.find_existing(&constant) {
|
||||
return (idx, false);
|
||||
}
|
||||
let idx = self.constants.len();
|
||||
@@ -95,14 +188,8 @@ impl ConstantPool {
|
||||
}
|
||||
|
||||
fn try_insert_full(&mut self, constant: ConstantData) -> crate::InternalResult<(usize, bool)> {
|
||||
// CPython's _PyCode_ConstantKey() keeps NaN-bearing constants distinct
|
||||
// because Python-level NaN keys do not compare equal.
|
||||
if !Self::constant_contains_nan(&constant)
|
||||
&& let Some(idx) = self
|
||||
.constants
|
||||
.iter()
|
||||
.position(|existing| existing == &constant)
|
||||
{
|
||||
let constant = Self::canonicalize_constant_key(constant)?;
|
||||
if let Some(idx) = self.find_existing(&constant) {
|
||||
return Ok((idx, false));
|
||||
}
|
||||
self.constants
|
||||
@@ -1485,6 +1572,7 @@ impl Blocks {
|
||||
| PseudoInstruction::JumpIfTrue { .. }),
|
||||
) => {
|
||||
let opcode = pseudo.into();
|
||||
let opcode_is_false = matches!(pseudo, PseudoInstruction::JumpIfFalse { .. });
|
||||
match target.instr.pseudo().map(Into::into) {
|
||||
Some(PseudoOpcode::Jump)
|
||||
if self.jump_thread(block_idx, i, &target, opcode)? =>
|
||||
@@ -1492,22 +1580,25 @@ impl Blocks {
|
||||
continue;
|
||||
}
|
||||
Some(PseudoOpcode::JumpIfFalse)
|
||||
if matches!(
|
||||
opcode,
|
||||
AnyInstruction::Pseudo(PseudoInstruction::JumpIfFalse { .. })
|
||||
) && self.jump_thread(block_idx, i, &target, opcode)? =>
|
||||
if opcode_is_false
|
||||
&& self.jump_thread(block_idx, i, &target, opcode)? =>
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Some(PseudoOpcode::JumpIfTrue)
|
||||
if matches!(
|
||||
opcode,
|
||||
AnyInstruction::Pseudo(PseudoInstruction::JumpIfTrue { .. })
|
||||
) && self.jump_thread(block_idx, i, &target, opcode)? =>
|
||||
if !opcode_is_false
|
||||
&& self.jump_thread(block_idx, i, &target, opcode)? =>
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Some(PseudoOpcode::JumpIfFalse | PseudoOpcode::JumpIfTrue) => {
|
||||
Some(PseudoOpcode::JumpIfTrue) if opcode_is_false => {
|
||||
let next = self[inst.target].next;
|
||||
debug_assert!(next != BlockIdx::NULL);
|
||||
debug_assert!(next != inst.target);
|
||||
self[block_idx].instructions[i].target = next;
|
||||
continue;
|
||||
}
|
||||
Some(PseudoOpcode::JumpIfFalse) if !opcode_is_false => {
|
||||
let next = self[inst.target].next;
|
||||
debug_assert!(next != BlockIdx::NULL);
|
||||
debug_assert!(next != inst.target);
|
||||
@@ -2335,10 +2426,10 @@ impl Blocks {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// flowgraph.c mark_cold (two-pass to match CPython).
|
||||
/// flowgraph.c mark_cold (two-pass).
|
||||
///
|
||||
/// Phase 1 (mark_warm): propagate "warm" from entry via fall-through and
|
||||
/// jump targets. CPython asserts while visiting warm blocks that they are not
|
||||
/// jump targets. The pass asserts while visiting warm blocks that they are not
|
||||
/// exception handlers.
|
||||
///
|
||||
/// Phase 2 (mark_cold): propagate "cold" from except_handler blocks via
|
||||
@@ -2349,7 +2440,7 @@ impl Blocks {
|
||||
/// empty unreachable placeholders left by remove_unreachable; they stay in
|
||||
/// their original chain position (e.g. between entry and the post-try
|
||||
/// continuation for a nested try/except whose inner_end was emptied by
|
||||
/// optimize_cfg). This matches CPython's behavior and is necessary for
|
||||
/// optimize_cfg). This is necessary for
|
||||
/// optimize_load_fast to terminate fall-through at those placeholders.
|
||||
/// flowgraph.c mark_warm
|
||||
fn mark_warm(&mut self) -> crate::InternalResult<()> {
|
||||
@@ -2981,42 +3072,12 @@ impl Blocks {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Block>> for Blocks {
|
||||
fn from(value: Vec<Block>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<[Block]>> for Blocks {
|
||||
fn from(value: Box<[Block]>) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[Block]> for Blocks {
|
||||
fn from(value: &[Block]) -> Self {
|
||||
Self(value.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&mut [Block]> for Blocks {
|
||||
fn from(value: &mut [Block]) -> Self {
|
||||
Self(value.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> From<[Block; N]> for Blocks {
|
||||
fn from(value: [Block; N]) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> From<&[Block; N]> for Blocks {
|
||||
fn from(value: &[Block; N]) -> Self {
|
||||
Self(value.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Blocks {
|
||||
type Target = [Block];
|
||||
|
||||
@@ -3060,7 +3121,7 @@ impl IndexMut<BlockIdx> for Blocks {
|
||||
}
|
||||
|
||||
pub(crate) const START_DEPTH_UNSET: i32 = i32::MIN;
|
||||
const CO_MAXBLOCKS: usize = 20;
|
||||
const CO_MAXBLOCKS: usize = 21;
|
||||
|
||||
/// flowgraph.c struct _PyCfgExceptStack
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -3099,7 +3160,7 @@ impl CfgTraversalStack {
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct InstructionSequenceLabelMap {
|
||||
block_labels: Vec<InstructionSequenceLabel>,
|
||||
/// Codegen-side shadow of CPython's instruction-sequence label map.
|
||||
/// Codegen-side shadow of the instruction-sequence label map.
|
||||
///
|
||||
/// `_PyInstructionSequence_UseLabel()` can map multiple labels to the same
|
||||
/// instruction offset before `_PyCfg_FromInstructionSequence()` materializes
|
||||
@@ -3305,7 +3366,7 @@ pub struct CodeInfo {
|
||||
|
||||
// Reference to the symbol table for this scope
|
||||
pub symbol_table_index: usize,
|
||||
// CPython compile.c uses PyList_GET_SIZE(u->u_ste->ste_varnames)
|
||||
// compile.c uses PyList_GET_SIZE(u->u_ste->ste_varnames)
|
||||
// when calling flowgraph.c _PyCfg_OptimizeCodeUnit().
|
||||
pub nparams: usize,
|
||||
|
||||
@@ -3488,7 +3549,7 @@ impl CodeInfo {
|
||||
}
|
||||
|
||||
fn prepare_cfg_from_codegen(&mut self) -> crate::InternalResult<InstructionSequence> {
|
||||
// CPython compile.c optimize_and_assemble_code_unit passes
|
||||
// compile.c optimize_and_assemble_code_unit passes
|
||||
// u_instr_sequence directly into flowgraph.c _PyCfg_FromInstructionSequence().
|
||||
self.take_recorded_instr_sequence()
|
||||
}
|
||||
@@ -3509,12 +3570,12 @@ fn optimize_code_unit(
|
||||
optimize_cfg(metadata, blocks, metadata.firstlineno)?;
|
||||
blocks.remove_unused_consts(&mut metadata.consts)?;
|
||||
add_checks_for_loads_of_uninitialized_variables(blocks, nlocals, nparams)?;
|
||||
// CPython inserts superinstructions in _PyCfg_OptimizeCodeUnit, before
|
||||
// Superinstructions are inserted in _PyCfg_OptimizeCodeUnit, before
|
||||
// later jump normalization / block reordering can create adjacencies
|
||||
// that never exist at this stage in flowgraph.c.
|
||||
blocks.insert_superinstructions()?;
|
||||
blocks.push_cold_blocks_to_end()?;
|
||||
// CPython resolves line numbers again after cold-block extraction.
|
||||
// Line numbers are resolved again after cold-block extraction.
|
||||
blocks.resolve_line_numbers(metadata.firstlineno)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -3525,20 +3586,20 @@ fn optimize_cfg(
|
||||
firstlineno: OneIndexed,
|
||||
) -> crate::InternalResult<()> {
|
||||
// flowgraph.c optimize_cfg
|
||||
// CPython optimize_cfg() starts with check_cfg() and raises
|
||||
// optimize_cfg() starts with check_cfg() and raises
|
||||
// SystemError if a jump or scope exit is not the last instruction in
|
||||
// its block.
|
||||
blocks.check_cfg()?;
|
||||
blocks.inline_small_or_no_lineno_blocks()?;
|
||||
// CPython does not re-run instruction-sequence label-map/CFG conversion
|
||||
// The instruction-sequence label-map/CFG conversion is not re-run
|
||||
// after this point. Unreferenced label blocks left by jump inlining
|
||||
// remain block boundaries and can preserve line-marker NOPs.
|
||||
blocks.remove_unreachable()?;
|
||||
// CPython optimize_cfg resolves line numbers before local checks and
|
||||
// optimize_cfg resolves line numbers before local checks and
|
||||
// superinstruction insertion, so fusion decisions see propagated
|
||||
// source locations.
|
||||
blocks.resolve_line_numbers(firstlineno)?;
|
||||
// CPython optimize_cfg() runs optimize_load_const() and then
|
||||
// optimize_cfg() runs optimize_load_const() and then
|
||||
// optimize_basic_block() after line numbers are resolved.
|
||||
optimize_load_const(metadata, blocks)?;
|
||||
let mut block_idx = BlockIdx(0);
|
||||
@@ -3548,7 +3609,7 @@ fn optimize_cfg(
|
||||
block_idx = next_block;
|
||||
}
|
||||
blocks.remove_redundant_nops_and_pairs()?;
|
||||
// CPython optimize_cfg() removes newly-unreachable blocks and
|
||||
// optimize_cfg() removes newly-unreachable blocks and
|
||||
// redundant NOP/jump chains before _PyCfg_OptimizeCodeUnit() prunes
|
||||
// unused constants.
|
||||
blocks.remove_unreachable()?;
|
||||
@@ -3568,7 +3629,7 @@ fn optimized_cfg_to_instruction_sequence(
|
||||
let max_stackdepth = blocks.calculate_stackdepth()?;
|
||||
debug_assert!(!is_generator(flags) || max_stackdepth != 0);
|
||||
let nlocalsplus = prepare_localsplus(metadata, blocks, flags)?;
|
||||
// Match CPython order: pseudo ops are lowered after stackdepth and
|
||||
// Pseudo ops are lowered after stackdepth and
|
||||
// localsplus preparation, before normalize_jumps.
|
||||
convert_pseudo_ops(blocks)?;
|
||||
blocks.normalize_jumps()?;
|
||||
@@ -3636,6 +3697,9 @@ impl CodeInfo {
|
||||
kwonlyargcount: kwonlyarg_count,
|
||||
firstlineno: first_line_number,
|
||||
} = metadata;
|
||||
let code_arg_count = posonlyarg_count
|
||||
.checked_add(arg_count)
|
||||
.ok_or(InternalError::MalformedControlFlowGraph)?;
|
||||
|
||||
resolve_unconditional_jumps(&mut instr_sequence)?;
|
||||
resolve_jump_offsets(&mut instr_sequence)?;
|
||||
@@ -3653,7 +3717,7 @@ impl CodeInfo {
|
||||
Ok(CodeObject {
|
||||
flags,
|
||||
posonlyarg_count,
|
||||
arg_count,
|
||||
arg_count: code_arg_count,
|
||||
kwonlyarg_count,
|
||||
source_path,
|
||||
first_line_number: Some(first_line_number),
|
||||
@@ -4109,8 +4173,13 @@ fn const_folding_safe_multiply(left: &ConstantData, right: &ConstantData) -> Opt
|
||||
const_folding_safe_multiply(right, left)
|
||||
}
|
||||
(ConstantData::Tuple { elements }, ConstantData::Integer { value: n }) => {
|
||||
if elements.is_empty() {
|
||||
return Some(ConstantData::Tuple {
|
||||
elements: Vec::new(),
|
||||
});
|
||||
}
|
||||
let n = n.to_usize()?;
|
||||
if n != 0 && !elements.is_empty() {
|
||||
if n != 0 {
|
||||
if n > MAX_COLLECTION_SIZE / elements.len() {
|
||||
return None;
|
||||
}
|
||||
@@ -4265,7 +4334,7 @@ fn eval_const_complex_binop(
|
||||
BinOp::Add => left + right,
|
||||
BinOp::Subtract => {
|
||||
let re = left.re - right.re;
|
||||
// Preserve CPython's signed-zero behavior for real-zero
|
||||
// Preserve signed-zero behavior for real-zero
|
||||
// minus zero-complex expressions such as `0 - 0j`.
|
||||
let im = if left.re == 0.0
|
||||
&& left.im == 0.0
|
||||
@@ -4829,7 +4898,7 @@ fn fold_constant_intrinsic_list_to_tuple(
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Port of CPython's flowgraph.c optimize_lists_and_sets().
|
||||
/// Port of flowgraph.c optimize_lists_and_sets().
|
||||
fn optimize_lists_and_sets(
|
||||
metadata: &mut CodeUnitMetadata,
|
||||
block: &mut Block,
|
||||
@@ -4889,8 +4958,7 @@ fn optimize_lists_and_sets(
|
||||
|
||||
if !contains_or_iter {
|
||||
debug_assert!(i >= 2);
|
||||
let folded_loc = block.instructions[i].location;
|
||||
let end_loc = block.instructions[i].end_location;
|
||||
let folded_loc = instr_location(&block.instructions[i]);
|
||||
|
||||
nop_out(block, &operand_indices);
|
||||
|
||||
@@ -4901,9 +4969,7 @@ fn optimize_lists_and_sets(
|
||||
}
|
||||
.into();
|
||||
instr_set_op1(&mut block.instructions[i - 2], build_instr, OpArg::new(0));
|
||||
block.instructions[i - 2].location = folded_loc;
|
||||
block.instructions[i - 2].end_location = end_loc;
|
||||
block.instructions[i - 2].lineno_override = None;
|
||||
instr_set_location(&mut block.instructions[i - 2], folded_loc);
|
||||
|
||||
instr_set_op1(
|
||||
&mut block.instructions[i - 1],
|
||||
@@ -5152,7 +5218,7 @@ fn basicblock_optimize_load_const(
|
||||
block: &mut Block,
|
||||
) -> crate::InternalResult<()> {
|
||||
let mut i = 0;
|
||||
let mut effective_opcode = None;
|
||||
let mut effective_opcode = Instruction::Nop.into();
|
||||
let mut effective_oparg = OpArg::new(0);
|
||||
while i < block.instruction_used {
|
||||
if matches!(
|
||||
@@ -5166,21 +5232,19 @@ fn basicblock_optimize_load_const(
|
||||
let curr = block.instructions[i];
|
||||
let curr_arg = curr.arg;
|
||||
|
||||
// Only combine if the source is a real instruction.
|
||||
let Some(curr_instr) = curr.instr.real() else {
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
|
||||
let is_copy_of_load_const = matches!(
|
||||
(effective_opcode, curr_instr),
|
||||
(Some(Instruction::LoadConst { .. }), Instruction::Copy { i }) if i.get(curr_arg) == 1
|
||||
(effective_opcode, curr.instr.real()),
|
||||
(AnyInstruction::Real(Instruction::LoadConst { .. }), Some(Instruction::Copy { i }))
|
||||
if i.get(curr_arg) == 1
|
||||
);
|
||||
if !is_copy_of_load_const {
|
||||
effective_opcode = Some(curr_instr);
|
||||
effective_opcode = curr.instr;
|
||||
effective_oparg = curr_arg;
|
||||
}
|
||||
let Some(const_instr) = effective_opcode else {
|
||||
debug_assert!(!effective_opcode.is_assembler());
|
||||
let Some(const_instr @ (Instruction::LoadConst { .. } | Instruction::LoadSmallInt { .. })) =
|
||||
effective_opcode.real()
|
||||
else {
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
@@ -5273,7 +5337,7 @@ fn basicblock_optimize_load_const(
|
||||
Opcode::PopJumpIfNone
|
||||
}
|
||||
.into();
|
||||
i = jump_idx;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -6557,21 +6621,19 @@ fn get_max_label(blocks: &Blocks) -> i32 {
|
||||
}
|
||||
|
||||
/// flowgraph.c make_except_stack
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn make_except_stack() -> crate::InternalResult<CfgExceptStack> {
|
||||
fn make_except_stack() -> CfgExceptStack {
|
||||
let handlers = [BlockIdx::NULL; CO_MAXBLOCKS + 2];
|
||||
debug_assert_eq!(handlers[0], BlockIdx::NULL);
|
||||
Ok(CfgExceptStack { handlers, depth: 0 })
|
||||
CfgExceptStack { handlers, depth: 0 }
|
||||
}
|
||||
|
||||
/// flowgraph.c copy_except_stack
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn copy_except_stack(stack: &CfgExceptStack) -> crate::InternalResult<CfgExceptStack> {
|
||||
fn copy_except_stack(stack: &CfgExceptStack) -> CfgExceptStack {
|
||||
debug_assert!(stack.depth <= CO_MAXBLOCKS + 1);
|
||||
Ok(CfgExceptStack {
|
||||
CfgExceptStack {
|
||||
handlers: stack.handlers,
|
||||
depth: stack.depth,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// flowgraph.c except_stack_top
|
||||
@@ -6623,7 +6685,7 @@ pub(crate) fn label_exception_targets(blocks: &mut Blocks) -> crate::InternalRes
|
||||
|
||||
todo.push(BlockIdx(0));
|
||||
blocks[0].visited = true;
|
||||
blocks[0].except_stack = Some(make_except_stack()?);
|
||||
blocks[0].except_stack = Some(make_except_stack());
|
||||
|
||||
while let Some(block_idx) = todo.pop() {
|
||||
let bi = block_idx.idx();
|
||||
@@ -6650,7 +6712,7 @@ pub(crate) fn label_exception_targets(blocks: &mut Blocks) -> crate::InternalRes
|
||||
if !blocks[target].visited {
|
||||
blocks[target].except_stack = Some(copy_except_stack(
|
||||
stack.as_ref().expect("active exception stack"),
|
||||
)?);
|
||||
));
|
||||
todo.push(target);
|
||||
blocks[target].visited = true;
|
||||
}
|
||||
@@ -6674,7 +6736,7 @@ pub(crate) fn label_exception_targets(blocks: &mut Blocks) -> crate::InternalRes
|
||||
if bb_has_fallthrough(&blocks[bi]) {
|
||||
blocks[target].except_stack = Some(copy_except_stack(
|
||||
stack.as_ref().expect("active exception stack"),
|
||||
)?);
|
||||
));
|
||||
} else {
|
||||
blocks[target].except_stack = stack.take();
|
||||
stack_transferred = true;
|
||||
@@ -6852,6 +6914,58 @@ pub(crate) fn fix_cell_offsets(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rustpython_compiler_core::bytecode::Arg;
|
||||
|
||||
fn int_const(value: i32) -> ConstantData {
|
||||
ConstantData::Integer {
|
||||
value: BigInt::from(value),
|
||||
}
|
||||
}
|
||||
|
||||
fn nan_const() -> ConstantData {
|
||||
ConstantData::Float { value: f64::NAN }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant_pool_frozenset_key_ignores_order_and_duplicates_like_cpython() {
|
||||
let mut pool = ConstantPool::default();
|
||||
let (first, inserted) = pool.insert_full(ConstantData::Frozenset {
|
||||
elements: vec![int_const(1), int_const(2)],
|
||||
});
|
||||
assert_eq!(first, 0);
|
||||
assert!(inserted);
|
||||
|
||||
let (second, inserted) = pool.insert_full(ConstantData::Frozenset {
|
||||
elements: vec![int_const(2), int_const(1), int_const(1)],
|
||||
});
|
||||
assert_eq!(
|
||||
second, first,
|
||||
"CPython _PyCode_ConstantKey uses frozenset item keys, not insertion order"
|
||||
);
|
||||
assert!(!inserted);
|
||||
assert!(matches!(
|
||||
&pool.constants[first],
|
||||
ConstantData::Frozenset { elements } if elements.len() == 2
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant_pool_frozenset_key_preserves_nan_duplicates_like_cpython() {
|
||||
let mut pool = ConstantPool::default();
|
||||
let (idx, inserted) = pool.insert_full(ConstantData::Frozenset {
|
||||
elements: vec![nan_const(), nan_const()],
|
||||
});
|
||||
|
||||
assert_eq!(idx, 0);
|
||||
assert!(inserted);
|
||||
assert!(matches!(
|
||||
&pool.constants[idx],
|
||||
ConstantData::Frozenset { elements }
|
||||
if elements.iter().filter(|constant| {
|
||||
matches!(constant, ConstantData::Float { value } if value.is_nan())
|
||||
}).count() == 2
|
||||
));
|
||||
}
|
||||
|
||||
fn test_location(line: u32) -> SourceLocation {
|
||||
SourceLocation {
|
||||
@@ -6886,6 +7000,13 @@ mod tests {
|
||||
instr
|
||||
}
|
||||
|
||||
fn test_true_cond_jump(target: BlockIdx, line: u32) -> InstructionInfo {
|
||||
let mut instr = test_instr(Instruction::Nop, line);
|
||||
instr.instr = PseudoOpcode::JumpIfTrue.into();
|
||||
instr.target = target;
|
||||
instr
|
||||
}
|
||||
|
||||
fn test_block_push(block: &mut Block, info: InstructionInfo) {
|
||||
let off = basicblock_next_instr(block).expect("test block instruction slot");
|
||||
block.instructions[off] = info;
|
||||
@@ -6970,7 +7091,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn except_stack_tracks_cpython_depth_and_handler_slots() {
|
||||
let mut stack = make_except_stack().unwrap();
|
||||
let mut stack = make_except_stack();
|
||||
assert_eq!(stack.depth, 0);
|
||||
assert_eq!(stack.handlers.len(), CO_MAXBLOCKS + 2);
|
||||
assert_eq!(stack.handlers[0], BlockIdx::NULL);
|
||||
@@ -6994,7 +7115,7 @@ mod tests {
|
||||
assert!(handler.preserve_lasti);
|
||||
assert!(blocks[1].preserve_lasti);
|
||||
|
||||
let copy = copy_except_stack(&stack).unwrap();
|
||||
let copy = copy_except_stack(&stack);
|
||||
assert_eq!(copy.depth, stack.depth);
|
||||
assert_eq!(copy.handlers, stack.handlers);
|
||||
|
||||
@@ -7399,6 +7520,52 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optimize_load_const_pseudo_opcode_breaks_effective_load_const() {
|
||||
let mut block = Block::default();
|
||||
test_block_push(
|
||||
&mut block,
|
||||
test_instr(
|
||||
Instruction::LoadConst {
|
||||
consti: Arg::marker(),
|
||||
},
|
||||
90,
|
||||
),
|
||||
);
|
||||
test_block_push(&mut block, test_true_cond_jump(BlockIdx::new(0), 90));
|
||||
let mut copy = test_instr(Instruction::Copy { i: Arg::marker() }, 90);
|
||||
copy.arg = OpArg::new(1);
|
||||
test_block_push(&mut block, copy);
|
||||
test_block_push(&mut block, test_instr(Instruction::ToBool, 90));
|
||||
|
||||
let mut code = test_code_info(block);
|
||||
let (const_idx, _) = code.metadata.consts.insert_full(ConstantData::Tuple {
|
||||
elements: vec![ConstantData::Integer {
|
||||
value: BigInt::from(1),
|
||||
}],
|
||||
});
|
||||
code.blocks[0].instructions[0].arg = OpArg::new(const_idx as u32);
|
||||
|
||||
optimize_load_const(&mut code.metadata, &mut code.blocks)
|
||||
.expect("optimize_load_const succeeds");
|
||||
|
||||
// `basicblock_optimize_load_const()` assigns the current
|
||||
// pseudo opcode to its effective opcode slot, so the following COPY 1
|
||||
// is not treated as a copy of the earlier LOAD_CONST.
|
||||
assert!(matches!(
|
||||
code.blocks[0].instructions[1].instr.pseudo(),
|
||||
Some(PseudoInstruction::Jump { .. })
|
||||
));
|
||||
assert!(matches!(
|
||||
code.blocks[0].instructions[2].instr.real(),
|
||||
Some(Instruction::Copy { .. })
|
||||
));
|
||||
assert!(matches!(
|
||||
code.blocks[0].instructions[3].instr.real(),
|
||||
Some(Instruction::ToBool)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optimize_load_fast_records_no_input_opcode_ref_at_cpython_produced_index() {
|
||||
let mut block = Block::default();
|
||||
@@ -7488,6 +7655,24 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_tuple_repeat_folds_negative_count_like_cpython() {
|
||||
let folded = const_folding_safe_multiply(
|
||||
&ConstantData::Tuple {
|
||||
elements: Vec::new(),
|
||||
},
|
||||
&ConstantData::Integer {
|
||||
value: BigInt::from(-1),
|
||||
},
|
||||
)
|
||||
.expect("CPython skips repeat-count checks for empty tuples");
|
||||
|
||||
assert!(matches!(
|
||||
folded,
|
||||
ConstantData::Tuple { elements } if elements.is_empty()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_line_numbers_duplicates_exit_blocks_like_cpython() {
|
||||
let exit = BlockIdx::new(2);
|
||||
@@ -7621,4 +7806,51 @@ mod tests {
|
||||
assert_eq!(threaded.target, BlockIdx::new(3));
|
||||
assert_eq!(u32::from(threaded.arg), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_direction_pseudo_conditional_jump_thread_false_keeps_target() {
|
||||
let mut blocks = Blocks::from([Block::default(), Block::default(), Block::default()]);
|
||||
for (i, block) in blocks.iter_mut().enumerate() {
|
||||
block.cpython_label = InstructionSequenceLabel::from_index(i as i32);
|
||||
}
|
||||
blocks[0].next = BlockIdx::new(1);
|
||||
blocks[1].next = BlockIdx::new(2);
|
||||
test_block_push(&mut blocks[0], test_cond_jump(BlockIdx::new(1), 10));
|
||||
test_block_push(&mut blocks[1], test_cond_jump(BlockIdx::new(1), 20));
|
||||
test_block_push(&mut blocks[2], test_instr(Instruction::ReturnValue, 30));
|
||||
|
||||
let mut metadata = test_code_info(Block::default()).metadata;
|
||||
blocks
|
||||
.optimize_basic_block(&mut metadata, BlockIdx::new(0))
|
||||
.expect("valid conditional jump chain");
|
||||
|
||||
// Only rewrite JUMP_IF_FALSE -> JUMP_IF_TRUE through
|
||||
// target->b_next. For same-direction jumps, a failed jump_thread()
|
||||
// leaves the original target unchanged.
|
||||
assert_eq!(blocks[0].instructions[0].target, BlockIdx::new(1));
|
||||
assert!(matches!(
|
||||
blocks[0].instructions[0].instr.pseudo(),
|
||||
Some(PseudoInstruction::JumpIfFalse { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opposite_direction_pseudo_conditional_uses_target_fallthrough() {
|
||||
let mut blocks = Blocks::from([Block::default(), Block::default(), Block::default()]);
|
||||
for (i, block) in blocks.iter_mut().enumerate() {
|
||||
block.cpython_label = InstructionSequenceLabel::from_index(i as i32);
|
||||
}
|
||||
blocks[0].next = BlockIdx::new(1);
|
||||
blocks[1].next = BlockIdx::new(2);
|
||||
test_block_push(&mut blocks[0], test_cond_jump(BlockIdx::new(1), 10));
|
||||
test_block_push(&mut blocks[1], test_true_cond_jump(BlockIdx::new(2), 20));
|
||||
test_block_push(&mut blocks[2], test_instr(Instruction::ReturnValue, 30));
|
||||
|
||||
let mut metadata = test_code_info(Block::default()).metadata;
|
||||
blocks
|
||||
.optimize_basic_block(&mut metadata, BlockIdx::new(0))
|
||||
.expect("valid conditional jump chain");
|
||||
|
||||
assert_eq!(blocks[0].instructions[0].target, BlockIdx::new(2));
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,15 @@ extern crate log;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use rustpython_compiler_core::bytecode::ConstantData;
|
||||
|
||||
type IndexMap<K, V> = indexmap::IndexMap<K, V, rapidhash::quality::RandomState>;
|
||||
type IndexSet<T> = indexmap::IndexSet<T, rapidhash::quality::RandomState>;
|
||||
|
||||
pub mod compile;
|
||||
pub mod error;
|
||||
pub mod ir;
|
||||
mod preprocess;
|
||||
pub mod preprocess;
|
||||
mod string_parser;
|
||||
pub mod symboltable;
|
||||
mod unparse;
|
||||
@@ -24,6 +26,73 @@ use ruff_python_ast as ast;
|
||||
|
||||
pub(crate) use compile::InternalResult;
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn constant_data_to_ast_constant_value(value: ConstantData) -> ast::ConstantValue {
|
||||
match value {
|
||||
ConstantData::None => ast::ConstantValue::None,
|
||||
ConstantData::Boolean { value } => ast::ConstantValue::Boolean(value),
|
||||
ConstantData::Str { value } => ast::ConstantValue::Str(value.to_string().into_boxed_str()),
|
||||
ConstantData::Bytes { value } => ast::ConstantValue::Bytes(value.into_boxed_slice()),
|
||||
ConstantData::Integer { value } => ast::ConstantValue::Integer(value.to_string().into()),
|
||||
ConstantData::Tuple { elements } => ast::ConstantValue::Tuple(
|
||||
elements
|
||||
.into_iter()
|
||||
.map(constant_data_to_ast_constant_value)
|
||||
.collect(),
|
||||
),
|
||||
ConstantData::Frozenset { elements } => ast::ConstantValue::Frozenset(
|
||||
elements
|
||||
.into_iter()
|
||||
.map(constant_data_to_ast_constant_value)
|
||||
.collect(),
|
||||
),
|
||||
ConstantData::Float { value } => ast::ConstantValue::Float(value),
|
||||
ConstantData::Complex { value } => ast::ConstantValue::Complex {
|
||||
real: value.re,
|
||||
imag: value.im,
|
||||
},
|
||||
ConstantData::Ellipsis => ast::ConstantValue::Ellipsis,
|
||||
ConstantData::Code { .. } | ConstantData::Slice { .. } => {
|
||||
unreachable!("ast.Constant values cannot contain code objects or slices")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ast_constant_value_to_constant_data(value: ast::ConstantValue) -> ConstantData {
|
||||
match value {
|
||||
ast::ConstantValue::None => ConstantData::None,
|
||||
ast::ConstantValue::Boolean(value) => ConstantData::Boolean { value },
|
||||
ast::ConstantValue::Str(value) => ConstantData::Str {
|
||||
value: value.to_string().into(),
|
||||
},
|
||||
ast::ConstantValue::Bytes(value) => ConstantData::Bytes {
|
||||
value: value.into_vec(),
|
||||
},
|
||||
ast::ConstantValue::Integer(value) => ConstantData::Integer {
|
||||
value: value
|
||||
.parse()
|
||||
.expect("RustPython ast.Constant integer values are decimal integers"),
|
||||
},
|
||||
ast::ConstantValue::Tuple(elements) => ConstantData::Tuple {
|
||||
elements: elements
|
||||
.into_iter()
|
||||
.map(ast_constant_value_to_constant_data)
|
||||
.collect(),
|
||||
},
|
||||
ast::ConstantValue::Frozenset(elements) => ConstantData::Frozenset {
|
||||
elements: elements
|
||||
.into_iter()
|
||||
.map(ast_constant_value_to_constant_data)
|
||||
.collect(),
|
||||
},
|
||||
ast::ConstantValue::Float(value) => ConstantData::Float { value },
|
||||
ast::ConstantValue::Complex { real, imag } => ConstantData::Complex {
|
||||
value: num_complex::Complex::new(real, imag),
|
||||
},
|
||||
ast::ConstantValue::Ellipsis => ConstantData::Ellipsis,
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToPythonName {
|
||||
/// Returns a short name for the node suitable for use in error messages.
|
||||
fn python_name(&self) -> &'static str;
|
||||
@@ -48,6 +117,19 @@ impl ToPythonName for ast::Expr {
|
||||
}
|
||||
Self::EllipsisLiteral(_) => "ellipsis",
|
||||
Self::NoneLiteral(_) => "None",
|
||||
Self::Constant(expr) => match &expr.value {
|
||||
ast::ConstantValue::None => "None",
|
||||
ast::ConstantValue::Boolean(true) => "True",
|
||||
ast::ConstantValue::Boolean(false) => "False",
|
||||
ast::ConstantValue::Ellipsis => "ellipsis",
|
||||
ast::ConstantValue::Tuple(_) => "tuple",
|
||||
ast::ConstantValue::Frozenset(_) => "literal",
|
||||
ast::ConstantValue::Str(_)
|
||||
| ast::ConstantValue::Bytes(_)
|
||||
| ast::ConstantValue::Integer(_)
|
||||
| ast::ConstantValue::Float(_)
|
||||
| ast::ConstantValue::Complex { .. } => "literal",
|
||||
},
|
||||
Self::NumberLiteral(_) | Self::BytesLiteral(_) | Self::StringLiteral(_) => "literal",
|
||||
Self::Tuple(_) => "tuple",
|
||||
Self::List { .. } => "list",
|
||||
@@ -65,7 +147,7 @@ impl ToPythonName for ast::Expr {
|
||||
Self::Lambda { .. } => "lambda",
|
||||
Self::If { .. } => "conditional expression",
|
||||
Self::Named { .. } => "named expression",
|
||||
Self::IpyEscapeCommand(_) => todo!(),
|
||||
Self::IpyEscapeCommand(_) => "expression",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,29 +8,401 @@ use ruff_python_ast::{
|
||||
visitor::transformer::{self, Transformer},
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use rustpython_compiler_core::bytecode;
|
||||
|
||||
const MAXDIGITS: usize = 3;
|
||||
const F_LJUST: u8 = 1;
|
||||
|
||||
pub(crate) fn preprocess_mod(module: &mut ast::Mod) {
|
||||
let preprocessor = AstPreprocessor;
|
||||
/// ast_preprocess.c ControlFlowInFinallyContext
|
||||
#[derive(Clone, Copy)]
|
||||
struct ControlFlowInFinallyContext {
|
||||
in_finally: bool,
|
||||
in_funcdef: bool,
|
||||
in_loop: bool,
|
||||
}
|
||||
|
||||
/// ast_preprocess.c before_return
|
||||
fn before_return<E>(
|
||||
contexts: &[ControlFlowInFinallyContext],
|
||||
range: TextRange,
|
||||
warn: &mut impl FnMut(TextRange, String) -> Result<(), E>,
|
||||
) -> Result<(), E> {
|
||||
if let Some(ctx) = contexts.last()
|
||||
&& ctx.in_finally
|
||||
&& !ctx.in_funcdef
|
||||
{
|
||||
warn(range, "'return' in a 'finally' block".to_owned())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ast_preprocess.c before_loop_exit
|
||||
fn before_loop_exit<E>(
|
||||
contexts: &[ControlFlowInFinallyContext],
|
||||
range: TextRange,
|
||||
kw: &str,
|
||||
warn: &mut impl FnMut(TextRange, String) -> Result<(), E>,
|
||||
) -> Result<(), E> {
|
||||
if let Some(ctx) = contexts.last()
|
||||
&& ctx.in_finally
|
||||
&& !ctx.in_loop
|
||||
{
|
||||
warn(range, format!("'{kw}' in a 'finally' block"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_body_with_control_flow_context<E>(
|
||||
body: &[ast::Stmt],
|
||||
contexts: &mut Vec<ControlFlowInFinallyContext>,
|
||||
warn: &mut impl FnMut(TextRange, String) -> Result<(), E>,
|
||||
in_finally: bool,
|
||||
in_funcdef: bool,
|
||||
in_loop: bool,
|
||||
) -> Result<(), E> {
|
||||
contexts.push(ControlFlowInFinallyContext {
|
||||
in_finally,
|
||||
in_funcdef,
|
||||
in_loop,
|
||||
});
|
||||
visit_body_for_control_flow_in_finally(body, contexts, warn)?;
|
||||
contexts.pop();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_body_for_control_flow_in_finally<E>(
|
||||
body: &[ast::Stmt],
|
||||
contexts: &mut Vec<ControlFlowInFinallyContext>,
|
||||
warn: &mut impl FnMut(TextRange, String) -> Result<(), E>,
|
||||
) -> Result<(), E> {
|
||||
for stmt in body {
|
||||
visit_stmt_for_control_flow_in_finally(stmt, contexts, warn)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ast_preprocess.c astfold_stmt control-flow warning traversal.
|
||||
fn visit_stmt_for_control_flow_in_finally<E>(
|
||||
stmt: &ast::Stmt,
|
||||
contexts: &mut Vec<ControlFlowInFinallyContext>,
|
||||
warn: &mut impl FnMut(TextRange, String) -> Result<(), E>,
|
||||
) -> Result<(), E> {
|
||||
match stmt {
|
||||
ast::Stmt::FunctionDef(function) => {
|
||||
visit_body_with_control_flow_context(
|
||||
&function.body,
|
||||
contexts,
|
||||
warn,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
ast::Stmt::ClassDef(class) => {
|
||||
visit_body_for_control_flow_in_finally(&class.body, contexts, warn)?;
|
||||
}
|
||||
ast::Stmt::Return(return_stmt) => {
|
||||
before_return(contexts, return_stmt.range, warn)?;
|
||||
}
|
||||
ast::Stmt::For(for_stmt) => {
|
||||
visit_body_with_control_flow_context(
|
||||
&for_stmt.body,
|
||||
contexts,
|
||||
warn,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
visit_body_for_control_flow_in_finally(&for_stmt.orelse, contexts, warn)?;
|
||||
}
|
||||
ast::Stmt::While(while_stmt) => {
|
||||
visit_body_with_control_flow_context(
|
||||
&while_stmt.body,
|
||||
contexts,
|
||||
warn,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
visit_body_for_control_flow_in_finally(&while_stmt.orelse, contexts, warn)?;
|
||||
}
|
||||
ast::Stmt::If(if_stmt) => {
|
||||
visit_body_for_control_flow_in_finally(&if_stmt.body, contexts, warn)?;
|
||||
for clause in &if_stmt.elif_else_clauses {
|
||||
visit_body_for_control_flow_in_finally(&clause.body, contexts, warn)?;
|
||||
}
|
||||
}
|
||||
ast::Stmt::Try(try_stmt) => {
|
||||
visit_body_for_control_flow_in_finally(&try_stmt.body, contexts, warn)?;
|
||||
for handler in &try_stmt.handlers {
|
||||
match handler {
|
||||
ast::ExceptHandler::ExceptHandler(handler) => {
|
||||
visit_body_for_control_flow_in_finally(&handler.body, contexts, warn)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
visit_body_for_control_flow_in_finally(&try_stmt.orelse, contexts, warn)?;
|
||||
visit_body_with_control_flow_context(
|
||||
&try_stmt.finalbody,
|
||||
contexts,
|
||||
warn,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
ast::Stmt::With(with_stmt) => {
|
||||
visit_body_for_control_flow_in_finally(&with_stmt.body, contexts, warn)?;
|
||||
}
|
||||
ast::Stmt::Match(match_stmt) => {
|
||||
for case in &match_stmt.cases {
|
||||
visit_body_for_control_flow_in_finally(&case.body, contexts, warn)?;
|
||||
}
|
||||
}
|
||||
ast::Stmt::Break(break_stmt) => {
|
||||
before_loop_exit(contexts, break_stmt.range, "break", warn)?;
|
||||
}
|
||||
ast::Stmt::Continue(continue_stmt) => {
|
||||
before_loop_exit(contexts, continue_stmt.range, "continue", warn)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ast_preprocess.c control_flow_in_finally_warning
|
||||
pub fn warn_control_flow_in_finally<E>(
|
||||
module: &ast::Mod,
|
||||
mut warn: impl FnMut(TextRange, String) -> Result<(), E>,
|
||||
) -> Result<(), E> {
|
||||
let mut contexts = Vec::new();
|
||||
match module {
|
||||
ast::Mod::Module(module) => preprocessor.visit_body(&mut module.body),
|
||||
ast::Mod::Module(module) => {
|
||||
visit_body_for_control_flow_in_finally(&module.body, &mut contexts, &mut warn)?;
|
||||
}
|
||||
ast::Mod::Expression(_) => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn has_future_annotations(module: &ast::Mod) -> bool {
|
||||
future_features(module).contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS)
|
||||
}
|
||||
|
||||
pub fn future_features(module: &ast::Mod) -> bytecode::CodeFlags {
|
||||
checked_future_features(module).unwrap_or_else(|err| err.features)
|
||||
}
|
||||
|
||||
pub struct FutureFeatureError {
|
||||
pub features: bytecode::CodeFlags,
|
||||
pub range: TextRange,
|
||||
pub kind: FutureFeatureErrorKind,
|
||||
}
|
||||
|
||||
pub enum FutureFeatureErrorKind {
|
||||
InvalidFeature(String),
|
||||
InvalidBraces,
|
||||
}
|
||||
|
||||
pub fn checked_future_features(
|
||||
module: &ast::Mod,
|
||||
) -> Result<bytecode::CodeFlags, FutureFeatureError> {
|
||||
let ast::Mod::Module(module) = module else {
|
||||
return Ok(bytecode::CodeFlags::empty());
|
||||
};
|
||||
checked_future_features_in_body(&module.body)
|
||||
}
|
||||
|
||||
pub fn checked_future_features_in_body(
|
||||
body: &[ast::Stmt],
|
||||
) -> Result<bytecode::CodeFlags, FutureFeatureError> {
|
||||
let mut future_features = bytecode::CodeFlags::empty();
|
||||
let mut statements = body.iter();
|
||||
if let Some(ast::Stmt::Expr(ast::StmtExpr { value, .. })) = statements.clone().next()
|
||||
&& string_literal_expr_value(value).is_some()
|
||||
{
|
||||
statements.next();
|
||||
}
|
||||
for statement in statements {
|
||||
match statement {
|
||||
ast::Stmt::ImportFrom(ast::StmtImportFrom {
|
||||
module,
|
||||
names,
|
||||
level,
|
||||
..
|
||||
}) if *level == 0 && module.as_ref().map(|id| id.as_str()) == Some("__future__") => {
|
||||
for alias in names {
|
||||
match alias.name.as_str() {
|
||||
"nested_scopes" | "generators" | "division" | "absolute_import"
|
||||
| "with_statement" | "print_function" | "unicode_literals"
|
||||
| "generator_stop" => {}
|
||||
"annotations" => {
|
||||
future_features.insert(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
|
||||
}
|
||||
// Accept the future feature name, but leave it
|
||||
// as a RustPython no-op.
|
||||
"barry_as_FLUFL" => {}
|
||||
"braces" => {
|
||||
return Err(FutureFeatureError {
|
||||
features: future_features,
|
||||
range: alias.range,
|
||||
kind: FutureFeatureErrorKind::InvalidBraces,
|
||||
});
|
||||
}
|
||||
other => {
|
||||
return Err(FutureFeatureError {
|
||||
features: future_features,
|
||||
range: alias.range,
|
||||
kind: FutureFeatureErrorKind::InvalidFeature(other.to_owned()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return Ok(future_features),
|
||||
}
|
||||
}
|
||||
Ok(future_features)
|
||||
}
|
||||
|
||||
pub fn preprocess_statements(
|
||||
body: &mut [ast::Stmt],
|
||||
optimize: u8,
|
||||
future_annotations: bool,
|
||||
syntax_check_only: bool,
|
||||
) {
|
||||
let preprocessor = AstPreprocessor {
|
||||
optimize,
|
||||
future_annotations,
|
||||
constant_folding: !syntax_check_only,
|
||||
};
|
||||
for stmt in body {
|
||||
preprocessor.visit_stmt(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn preprocess_mod(
|
||||
module: &mut ast::Mod,
|
||||
optimize: u8,
|
||||
future_annotations: bool,
|
||||
syntax_check_only: bool,
|
||||
) {
|
||||
let preprocessor = AstPreprocessor {
|
||||
optimize,
|
||||
future_annotations,
|
||||
constant_folding: !syntax_check_only,
|
||||
};
|
||||
match module {
|
||||
ast::Mod::Module(module) => preprocessor.visit_astfold_body(&mut module.body),
|
||||
ast::Mod::Expression(expr) => preprocessor.visit_expr(&mut expr.body),
|
||||
}
|
||||
}
|
||||
|
||||
struct AstPreprocessor;
|
||||
struct AstPreprocessor {
|
||||
optimize: u8,
|
||||
future_annotations: bool,
|
||||
constant_folding: bool,
|
||||
}
|
||||
|
||||
impl Transformer for AstPreprocessor {
|
||||
fn visit_expr(&self, expr: &mut Expr) {
|
||||
transformer::walk_expr(self, expr);
|
||||
if let Some(optimized) = optimize_format(expr) {
|
||||
*expr = optimized;
|
||||
impl AstPreprocessor {
|
||||
fn visit_astfold_body(&self, body: &mut ast::Suite) {
|
||||
let mut docstring = body_starts_with_docstring(body);
|
||||
if docstring && self.optimize >= 2 {
|
||||
remove_docstring_from_body(body);
|
||||
docstring = false;
|
||||
}
|
||||
|
||||
for stmt in body.iter_mut() {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
|
||||
if !docstring && body_starts_with_docstring(body) {
|
||||
wrap_first_docstring_as_fstring(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transformer for AstPreprocessor {
|
||||
fn visit_stmt(&self, stmt: &mut ast::Stmt) {
|
||||
match stmt {
|
||||
ast::Stmt::FunctionDef(function) => {
|
||||
if let Some(type_params) = &mut function.type_params {
|
||||
self.visit_type_params(type_params);
|
||||
}
|
||||
self.visit_parameters(&mut function.parameters);
|
||||
self.visit_astfold_body(&mut function.body);
|
||||
for decorator in &mut function.decorator_list {
|
||||
self.visit_decorator(decorator);
|
||||
}
|
||||
if let Some(returns) = &mut function.returns {
|
||||
self.visit_annotation(returns);
|
||||
}
|
||||
}
|
||||
ast::Stmt::ClassDef(class) => {
|
||||
if let Some(type_params) = &mut class.type_params {
|
||||
self.visit_type_params(type_params);
|
||||
}
|
||||
if let Some(arguments) = &mut class.arguments {
|
||||
self.visit_arguments(arguments);
|
||||
}
|
||||
self.visit_astfold_body(&mut class.body);
|
||||
for decorator in &mut class.decorator_list {
|
||||
self.visit_decorator(decorator);
|
||||
}
|
||||
}
|
||||
_ => transformer::walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_annotation(&self, expr: &mut Expr) {
|
||||
if !self.future_annotations {
|
||||
transformer::walk_annotation(self, expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_pattern(&self, pattern: &mut ast::Pattern) {
|
||||
transformer::walk_pattern(self, pattern);
|
||||
if !self.constant_folding {
|
||||
return;
|
||||
}
|
||||
match pattern {
|
||||
ast::Pattern::MatchValue(value) => fold_match_value_constant_expr(&mut value.value),
|
||||
ast::Pattern::MatchMapping(mapping) => {
|
||||
for key in &mut mapping.keys {
|
||||
fold_match_value_constant_expr(key);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&self, expr: &mut Expr) {
|
||||
transformer::walk_expr(self, expr);
|
||||
if self.constant_folding {
|
||||
if let Some(optimized) = optimize_format(expr) {
|
||||
*expr = optimized;
|
||||
} else if let Some(optimized) = fold_debug_constant(expr, self.optimize) {
|
||||
*expr = optimized;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_debug_constant(expr: &Expr, optimize: u8) -> Option<Expr> {
|
||||
let Expr::Name(name) = expr else {
|
||||
return None;
|
||||
};
|
||||
if !matches!(name.ctx, ast::ExprContext::Load) || name.id.as_str() != "__debug__" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Expr::BooleanLiteral(ast::ExprBooleanLiteral {
|
||||
node_index: name.node_index.clone(),
|
||||
range: name.range,
|
||||
value: optimize == 0,
|
||||
}))
|
||||
}
|
||||
|
||||
fn optimize_format(expr: &Expr) -> Option<Expr> {
|
||||
let Expr::BinOp(binop) = expr else {
|
||||
return None;
|
||||
@@ -38,9 +410,7 @@ fn optimize_format(expr: &Expr) -> Option<Expr> {
|
||||
if !matches!(binop.op, Operator::Mod) {
|
||||
return None;
|
||||
}
|
||||
let Expr::StringLiteral(format) = binop.left.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
let (format, _) = string_literal_expr_value(&binop.left)?;
|
||||
let Expr::Tuple(tuple) = binop.right.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
@@ -52,7 +422,7 @@ fn optimize_format(expr: &Expr) -> Option<Expr> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let elements = parse_format(format.value.to_str(), &tuple.elts)?;
|
||||
let elements = parse_format(format, &tuple.elts)?;
|
||||
Some(Expr::FString(ExprFString {
|
||||
node_index: binop.node_index.clone(),
|
||||
range: binop.range,
|
||||
@@ -62,6 +432,8 @@ fn optimize_format(expr: &Expr) -> Option<Expr> {
|
||||
elements: InterpolatedStringElements::from(elements),
|
||||
flags: FStringFlags::empty(),
|
||||
}),
|
||||
runtime_joined_str: None,
|
||||
runtime_values: None,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -163,6 +535,9 @@ fn parse_format_arg(chars: &[char], pos: &mut usize, arg: Expr) -> Option<Interp
|
||||
debug_text: None,
|
||||
conversion,
|
||||
format_spec,
|
||||
runtime_str: None,
|
||||
runtime_interpolation_format_spec: None,
|
||||
runtime_formatted_value_format_spec: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -223,3 +598,396 @@ fn generated_literal(value: String) -> InterpolatedStringLiteralElement {
|
||||
value: value.into_boxed_str(),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_docstring_from_body(body: &mut ast::Suite) {
|
||||
if let Some(range) = take_docstring(body) {
|
||||
if !body.is_empty() {
|
||||
return;
|
||||
}
|
||||
let start = range.start();
|
||||
let pass_range = TextRange::new(start, start + ruff_text_size::TextSize::from(4));
|
||||
body.push(ast::Stmt::Pass(ast::StmtPass {
|
||||
node_index: Default::default(),
|
||||
range: pass_range,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
fn take_docstring(body: &mut ast::Suite) -> Option<TextRange> {
|
||||
let ast::Stmt::Expr(expr_stmt) = body.first()? else {
|
||||
return None;
|
||||
};
|
||||
if let Some((_, range)) = string_literal_expr_value(&expr_stmt.value) {
|
||||
body.remove(0);
|
||||
return Some(range);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn body_starts_with_docstring(body: &[ast::Stmt]) -> bool {
|
||||
let Some(ast::Stmt::Expr(expr_stmt)) = body.first() else {
|
||||
return false;
|
||||
};
|
||||
string_literal_expr_value(&expr_stmt.value).is_some()
|
||||
}
|
||||
|
||||
fn wrap_first_docstring_as_fstring(body: &mut [ast::Stmt]) {
|
||||
let Some(ast::Stmt::Expr(expr_stmt)) = body.first_mut() else {
|
||||
return;
|
||||
};
|
||||
let Some((value, range)) = string_literal_expr_value(&expr_stmt.value) else {
|
||||
return;
|
||||
};
|
||||
let value = value.to_string();
|
||||
*expr_stmt.value = ast::Expr::FString(ast::ExprFString {
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
range,
|
||||
value: FStringValue::single(FString {
|
||||
range,
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
elements: InterpolatedStringElements::from(vec![InterpolatedStringElement::Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range,
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
value: value.into_boxed_str(),
|
||||
},
|
||||
)]),
|
||||
flags: FStringFlags::empty(),
|
||||
}),
|
||||
runtime_joined_str: None,
|
||||
runtime_values: None,
|
||||
});
|
||||
}
|
||||
|
||||
fn string_literal_expr_value(expr: &Expr) -> Option<(&str, TextRange)> {
|
||||
match expr {
|
||||
Expr::StringLiteral(string) => Some((string.value.to_str(), expr.range())),
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: ast::ConstantValue::Str(value),
|
||||
..
|
||||
}) => Some((value.as_ref(), expr.range())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_match_value_constant_expr(expr: &mut ast::Expr) {
|
||||
match expr {
|
||||
ast::Expr::UnaryOp(unary)
|
||||
if matches!(unary.op, ast::UnaryOp::USub)
|
||||
&& matches!(unary.operand.as_ref(), ast::Expr::NumberLiteral(_)) =>
|
||||
{
|
||||
if let Some(number) = negate_match_number(&unary.operand) {
|
||||
*expr = ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
node_index: unary.node_index.clone(),
|
||||
range: unary.range,
|
||||
value: number,
|
||||
});
|
||||
}
|
||||
}
|
||||
ast::Expr::BinOp(binop) if matches!(binop.op, ast::Operator::Add | ast::Operator::Sub) => {
|
||||
fold_match_value_constant_expr(&mut binop.left);
|
||||
if let Some(number) = fold_match_number_binop(&binop.left, binop.op, &binop.right) {
|
||||
*expr = ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
node_index: binop.node_index.clone(),
|
||||
range: binop.range,
|
||||
value: number,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn negate_match_number(expr: &ast::Expr) -> Option<ast::Number> {
|
||||
let ast::Expr::NumberLiteral(number) = expr else {
|
||||
return None;
|
||||
};
|
||||
Some(match &number.value {
|
||||
ast::Number::Int(value) => {
|
||||
if *value == ast::Int::ZERO {
|
||||
ast::Number::Int(ast::Int::ZERO)
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
ast::Number::Float(value) => ast::Number::Float(-value),
|
||||
ast::Number::Complex { real, imag } => ast::Number::Complex {
|
||||
real: -real,
|
||||
imag: -imag,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn fold_match_number_binop(
|
||||
left: &ast::Expr,
|
||||
op: ast::Operator,
|
||||
right: &ast::Expr,
|
||||
) -> Option<ast::Number> {
|
||||
let ast::Expr::NumberLiteral(left) = left else {
|
||||
return None;
|
||||
};
|
||||
let ast::Expr::NumberLiteral(right) = right else {
|
||||
return None;
|
||||
};
|
||||
let right = match right.value {
|
||||
ast::Number::Complex { real, imag } => (real, imag),
|
||||
_ => return None,
|
||||
};
|
||||
enum MatchNumberLeft {
|
||||
Real(f64),
|
||||
Complex { real: f64, imag: f64 },
|
||||
}
|
||||
let left = match &left.value {
|
||||
ast::Number::Int(value) => MatchNumberLeft::Real(value.as_i64()? as f64),
|
||||
ast::Number::Float(value) => MatchNumberLeft::Real(*value),
|
||||
ast::Number::Complex { real, imag } => MatchNumberLeft::Complex {
|
||||
real: *real,
|
||||
imag: *imag,
|
||||
},
|
||||
};
|
||||
let (real, imag) = match (left, op) {
|
||||
(MatchNumberLeft::Real(left), ast::Operator::Add) => (left + right.0, right.1),
|
||||
(MatchNumberLeft::Real(left), ast::Operator::Sub) => (left - right.0, -right.1),
|
||||
(MatchNumberLeft::Complex { real, imag }, ast::Operator::Add) => {
|
||||
(real + right.0, imag + right.1)
|
||||
}
|
||||
(MatchNumberLeft::Complex { real, imag }, ast::Operator::Sub) => {
|
||||
(real - right.0, imag - right.1)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(ast::Number::Complex { real, imag })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn first_match_value(source: &str) -> ast::Expr {
|
||||
let parsed = ruff_python_parser::parse(source, ruff_python_parser::Mode::Module.into())
|
||||
.unwrap()
|
||||
.into_syntax();
|
||||
let mut module = parsed;
|
||||
let future_annotations = has_future_annotations(&module);
|
||||
preprocess_mod(&mut module, 0, future_annotations, false);
|
||||
let ast::Mod::Module(module) = module else {
|
||||
panic!("expected module");
|
||||
};
|
||||
let [ast::Stmt::Match(match_stmt)] = &module.body[..] else {
|
||||
panic!("expected a single match statement");
|
||||
};
|
||||
let ast::Pattern::MatchValue(value) = &match_stmt.cases[0].pattern else {
|
||||
panic!("expected a value pattern");
|
||||
};
|
||||
*value.value.clone()
|
||||
}
|
||||
|
||||
fn preprocess_source(source: &str) -> ast::Mod {
|
||||
let mut module = ruff_python_parser::parse(source, ruff_python_parser::Mode::Module.into())
|
||||
.unwrap()
|
||||
.into_syntax();
|
||||
let future_annotations = has_future_annotations(&module);
|
||||
preprocess_mod(&mut module, 0, future_annotations, false);
|
||||
module
|
||||
}
|
||||
|
||||
fn preprocess_source_with_optimize(source: &str, optimize: u8) -> ast::Mod {
|
||||
let mut module = ruff_python_parser::parse(source, ruff_python_parser::Mode::Module.into())
|
||||
.unwrap()
|
||||
.into_syntax();
|
||||
let future_annotations = has_future_annotations(&module);
|
||||
preprocess_mod(&mut module, optimize, future_annotations, false);
|
||||
module
|
||||
}
|
||||
|
||||
fn preprocess_source_syntax_check_only(source: &str, optimize: u8) -> ast::Mod {
|
||||
let mut module = ruff_python_parser::parse(source, ruff_python_parser::Mode::Module.into())
|
||||
.unwrap()
|
||||
.into_syntax();
|
||||
let future_annotations = has_future_annotations(&module);
|
||||
preprocess_mod(&mut module, optimize, future_annotations, true);
|
||||
module
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folds_match_value_negative_float_in_preprocess() {
|
||||
let value = first_match_value(
|
||||
"\
|
||||
match value:
|
||||
case -1.5:
|
||||
pass
|
||||
",
|
||||
);
|
||||
let ast::Expr::NumberLiteral(number) = value else {
|
||||
panic!("expected folded number literal, got {value:?}");
|
||||
};
|
||||
assert!(matches!(number.value, ast::Number::Float(value) if value == -1.5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folds_match_value_complex_binop_in_preprocess() {
|
||||
let value = first_match_value(
|
||||
"\
|
||||
match value:
|
||||
case 1 + 2j:
|
||||
pass
|
||||
",
|
||||
);
|
||||
let ast::Expr::NumberLiteral(number) = value else {
|
||||
panic!("expected folded number literal, got {value:?}");
|
||||
};
|
||||
assert!(
|
||||
matches!(number.value, ast::Number::Complex { real, imag } if real == 1.0 && imag == 2.0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folds_match_value_complex_complex_binop_in_preprocess() {
|
||||
let left = ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
range: TextRange::default(),
|
||||
value: ast::Number::Complex {
|
||||
real: 0.0,
|
||||
imag: 1.0,
|
||||
},
|
||||
});
|
||||
let right = ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
range: TextRange::default(),
|
||||
value: ast::Number::Complex {
|
||||
real: 0.0,
|
||||
imag: 2.0,
|
||||
},
|
||||
});
|
||||
let number = fold_match_number_binop(&left, ast::Operator::Add, &right)
|
||||
.expect("CPython fold_const_match_patterns() uses PyNumber_Add");
|
||||
assert!(
|
||||
matches!(number, ast::Number::Complex { real, imag } if real == 0.0 && imag == 3.0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folds_match_value_real_minus_zero_complex_preserves_negative_zero_in_preprocess() {
|
||||
let value = first_match_value(
|
||||
"\
|
||||
match value:
|
||||
case 0 - 0j:
|
||||
pass
|
||||
",
|
||||
);
|
||||
let ast::Expr::NumberLiteral(number) = value else {
|
||||
panic!("expected folded number literal, got {value:?}");
|
||||
};
|
||||
assert!(matches!(number.value, ast::Number::Complex { real, imag }
|
||||
if real == 0.0 && imag == 0.0 && imag.is_sign_negative()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn future_annotations_skip_annotation_preprocess_like_cpython() {
|
||||
let module = preprocess_source(
|
||||
"\
|
||||
from __future__ import annotations
|
||||
def f(x: __debug__) -> __debug__:
|
||||
pass
|
||||
y: __debug__
|
||||
z = __debug__
|
||||
",
|
||||
);
|
||||
let ast::Mod::Module(module) = module else {
|
||||
panic!("expected module");
|
||||
};
|
||||
let ast::Stmt::FunctionDef(function) = &module.body[1] else {
|
||||
panic!("expected function");
|
||||
};
|
||||
let annotation = function.parameters.args[0]
|
||||
.parameter
|
||||
.annotation
|
||||
.as_deref()
|
||||
.expect("missing parameter annotation");
|
||||
assert!(
|
||||
matches!(annotation, ast::Expr::Name(name) if name.id.as_str() == "__debug__"),
|
||||
"future annotations should skip parameter annotation folding, got {annotation:?}"
|
||||
);
|
||||
let returns = function
|
||||
.returns
|
||||
.as_deref()
|
||||
.expect("missing return annotation");
|
||||
assert!(
|
||||
matches!(returns, ast::Expr::Name(name) if name.id.as_str() == "__debug__"),
|
||||
"future annotations should skip return annotation folding, got {returns:?}"
|
||||
);
|
||||
let ast::Stmt::AnnAssign(ann_assign) = &module.body[2] else {
|
||||
panic!("expected annotated assignment");
|
||||
};
|
||||
assert!(
|
||||
matches!(ann_assign.annotation.as_ref(), ast::Expr::Name(name) if name.id.as_str() == "__debug__"),
|
||||
"future annotations should skip annotated assignment annotation folding, got {:?}",
|
||||
ann_assign.annotation
|
||||
);
|
||||
let ast::Stmt::Assign(assign) = &module.body[3] else {
|
||||
panic!("expected assignment");
|
||||
};
|
||||
assert!(
|
||||
matches!(assign.value.as_ref(), ast::Expr::BooleanLiteral(boolean) if boolean.value),
|
||||
"non-annotation expression should still fold __debug__, got {:?}",
|
||||
assign.value
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn late_future_annotations_do_not_affect_preprocess_like_cpython() {
|
||||
let module = preprocess_source(
|
||||
"\
|
||||
x = 1
|
||||
from __future__ import annotations
|
||||
y: __debug__
|
||||
",
|
||||
);
|
||||
let ast::Mod::Module(module) = module else {
|
||||
panic!("expected module");
|
||||
};
|
||||
let ast::Stmt::AnnAssign(ann_assign) = &module.body[2] else {
|
||||
panic!("expected annotated assignment");
|
||||
};
|
||||
assert!(
|
||||
matches!(ann_assign.annotation.as_ref(), ast::Expr::BooleanLiteral(boolean) if boolean.value),
|
||||
"late future import should not disable annotation folding, got {:?}",
|
||||
ann_assign.annotation
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optimize_two_wraps_new_docstring_after_removing_original() {
|
||||
let module = preprocess_source_with_optimize("\"first\"\n\"second\"\n", 2);
|
||||
let ast::Mod::Module(module) = module else {
|
||||
panic!("expected module");
|
||||
};
|
||||
let [ast::Stmt::Expr(expr)] = &module.body[..] else {
|
||||
panic!("expected only the second statement to remain");
|
||||
};
|
||||
assert!(
|
||||
matches!(expr.value.as_ref(), ast::Expr::FString(_)),
|
||||
"CPython wraps the new leading string as JoinedStr so it is not a docstring"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_check_only_disables_constant_folding_but_keeps_docstring_strip() {
|
||||
let module = preprocess_source_syntax_check_only("\"doc\"\nvalue = __debug__\n", 2);
|
||||
let ast::Mod::Module(module) = module else {
|
||||
panic!("expected module");
|
||||
};
|
||||
assert!(
|
||||
matches!(module.body[0], ast::Stmt::Assign(_)),
|
||||
"optimize=2 should still strip docstrings in syntax_check_only mode"
|
||||
);
|
||||
let ast::Stmt::Assign(assign) = &module.body[0] else {
|
||||
panic!("expected assignment");
|
||||
};
|
||||
assert!(
|
||||
matches!(assign.value.as_ref(), ast::Expr::Name(name) if name.id.as_str() == "__debug__"),
|
||||
"syntax_check_only should skip __debug__ folding, got {:?}",
|
||||
assign.value
|
||||
);
|
||||
}
|
||||
}
|
||||
+2127
-1184
File diff suppressed because it is too large.
Load diff
@@ -58,6 +58,63 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
self.f.write_fmt(f)
|
||||
}
|
||||
|
||||
fn unparse_float(&mut self, value: f64) -> fmt::Result {
|
||||
#[allow(clippy::correctness, clippy::assertions_on_constants)]
|
||||
const {
|
||||
assert!(f64::MAX_10_EXP == 308)
|
||||
};
|
||||
|
||||
if value.is_infinite() {
|
||||
self.p("1e309")
|
||||
} else {
|
||||
self.p(&rustpython_literal::float::to_string(value))
|
||||
}
|
||||
}
|
||||
|
||||
fn unparse_complex(&mut self, real: f64, imag: f64) -> fmt::Result {
|
||||
self.p(&rustpython_literal::complex::to_string(real, imag).replace("inf", "1e309"))
|
||||
}
|
||||
|
||||
fn unparse_constant_value(&mut self, value: &ast::ConstantValue) -> fmt::Result {
|
||||
match value {
|
||||
ast::ConstantValue::None => self.p("None"),
|
||||
ast::ConstantValue::Boolean(value) => self.p(if *value { "True" } else { "False" }),
|
||||
ast::ConstantValue::Str(value) => UnicodeEscape::new_repr(value.as_ref().into())
|
||||
.str_repr()
|
||||
.fmt(self.f),
|
||||
ast::ConstantValue::Bytes(value) => AsciiEscape::new_repr(value.as_ref())
|
||||
.bytes_repr()
|
||||
.fmt(self.f),
|
||||
ast::ConstantValue::Integer(value) => self.p(value.as_ref()),
|
||||
ast::ConstantValue::Tuple(elements) => {
|
||||
self.p("(")?;
|
||||
let mut first = true;
|
||||
for element in elements {
|
||||
self.p_delim(&mut first, ", ")?;
|
||||
self.unparse_constant_value(element)?;
|
||||
}
|
||||
self.p_if(elements.len() == 1, ",")?;
|
||||
self.p(")")
|
||||
}
|
||||
ast::ConstantValue::Frozenset(elements) => {
|
||||
if elements.is_empty() {
|
||||
self.p("frozenset()")
|
||||
} else {
|
||||
self.p("frozenset({")?;
|
||||
let mut first = true;
|
||||
for element in elements {
|
||||
self.p_delim(&mut first, ", ")?;
|
||||
self.unparse_constant_value(element)?;
|
||||
}
|
||||
self.p("})")
|
||||
}
|
||||
}
|
||||
ast::ConstantValue::Float(value) => self.unparse_float(*value),
|
||||
ast::ConstantValue::Complex { real, imag } => self.unparse_complex(*real, *imag),
|
||||
ast::ConstantValue::Ellipsis => self.p("..."),
|
||||
}
|
||||
}
|
||||
|
||||
fn unparse_expr(&mut self, ast: &ast::Expr, level: u8) -> fmt::Result {
|
||||
macro_rules! op_prec {
|
||||
($op_ty:ident, $x:expr, $enu:path, $($var:ident($op:literal, $prec:ident)),*$(,)?) => {
|
||||
@@ -87,6 +144,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
values,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
let (op, prec) = op_prec!(bin, op, ast::BoolOp, And("and", AND), Or("or", OR));
|
||||
group_if!(prec, {
|
||||
@@ -102,6 +160,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
value,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
group_if!(precedence::TUPLE, {
|
||||
self.unparse_expr(target, precedence::ATOM)?;
|
||||
@@ -115,6 +174,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
right,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
let right_associative = matches!(op, ast::Operator::Pow);
|
||||
let (op, prec) = op_prec!(
|
||||
@@ -146,6 +206,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
operand,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
let (op, prec) = op_prec!(
|
||||
un,
|
||||
@@ -166,6 +227,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
body,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
group_if!(precedence::TEST, {
|
||||
if let Some(parameters) = parameters {
|
||||
@@ -183,6 +245,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
orelse,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
group_if!(precedence::TEST, {
|
||||
self.unparse_expr(body, precedence::TEST + 1)?;
|
||||
@@ -196,6 +259,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
items,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
self.p("{")?;
|
||||
let mut first = true;
|
||||
@@ -214,6 +278,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
elts,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
self.p("{")?;
|
||||
let mut first = true;
|
||||
@@ -228,6 +293,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
generators,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
self.p("[")?;
|
||||
self.unparse_expr(elt, precedence::TEST)?;
|
||||
@@ -239,6 +305,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
generators,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
self.p("{")?;
|
||||
self.unparse_expr(elt, precedence::TEST)?;
|
||||
@@ -251,6 +318,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
generators,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
self.p("{")?;
|
||||
self.unparse_expr(key, precedence::TEST)?;
|
||||
@@ -265,6 +333,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
generators,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
self.p("(")?;
|
||||
self.unparse_expr(elt, precedence::TEST)?;
|
||||
@@ -275,6 +344,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
value,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
group_if!(precedence::AWAIT, {
|
||||
self.p("await ")?;
|
||||
@@ -285,6 +355,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
value,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
if let Some(value) = value {
|
||||
write!(self, "(yield {})", UnparseExpr::new(value, self.source))?;
|
||||
@@ -296,6 +367,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
value,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
write!(
|
||||
self,
|
||||
@@ -309,6 +381,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
comparators,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
group_if!(precedence::CMP, {
|
||||
let new_lvl = precedence::CMP + 1;
|
||||
@@ -326,6 +399,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
arguments: ast::Arguments { args, keywords, .. },
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
self.unparse_expr(func, precedence::ATOM)?;
|
||||
self.p("(")?;
|
||||
@@ -379,26 +453,13 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
.bytes_repr()
|
||||
.fmt(self.f)?
|
||||
}
|
||||
ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
|
||||
#[allow(clippy::correctness, clippy::assertions_on_constants)]
|
||||
const {
|
||||
assert!(f64::MAX_10_EXP == 308)
|
||||
};
|
||||
|
||||
let inf_str = "1e309";
|
||||
match value {
|
||||
ast::Number::Int(int) => int.fmt(self.f)?,
|
||||
&ast::Number::Float(fp) => {
|
||||
if fp.is_infinite() {
|
||||
self.p(inf_str)?
|
||||
} else {
|
||||
self.p(&rustpython_literal::float::to_string(fp))?
|
||||
}
|
||||
}
|
||||
&ast::Number::Complex { real, imag } => self
|
||||
.p(&rustpython_literal::complex::to_string(real, imag)
|
||||
.replace("inf", inf_str))?,
|
||||
}
|
||||
ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => match value {
|
||||
ast::Number::Int(int) => int.fmt(self.f)?,
|
||||
&ast::Number::Float(fp) => self.unparse_float(fp)?,
|
||||
&ast::Number::Complex { real, imag } => self.unparse_complex(real, imag)?,
|
||||
},
|
||||
ast::Expr::Constant(ast::ExprConstant { value, .. }) => {
|
||||
self.unparse_constant_value(value)?
|
||||
}
|
||||
ast::Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) => {
|
||||
self.p(if *value { "True" } else { "False" })?
|
||||
@@ -460,6 +521,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
step,
|
||||
node_index: _,
|
||||
range: _range,
|
||||
..
|
||||
}) => {
|
||||
if let Some(lower) = lower {
|
||||
self.unparse_expr(lower, precedence::TEST)?;
|
||||
@@ -554,7 +616,9 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> {
|
||||
let buffered =
|
||||
fmt::from_fn(|f| Unparser::new(f, self.source).unparse_expr(val, precedence::TEST + 1))
|
||||
.to_string();
|
||||
if let Some(ast::DebugText { leading, trailing }) = debug_text {
|
||||
if let Some(debug_text) = debug_text {
|
||||
let leading = debug_text.leading.as_str();
|
||||
let trailing = debug_text.trailing.as_str();
|
||||
self.p(leading)?;
|
||||
self.p(self.source.slice(val.range()))?;
|
||||
self.p(trailing)?;
|
||||
|
||||
@@ -468,6 +468,13 @@ bitflags! {
|
||||
const COROUTINE = 0x0080;
|
||||
const ITERABLE_COROUTINE = 0x0100;
|
||||
const ASYNC_GENERATOR = 0x0200;
|
||||
const FUTURE_DIVISION = 0x20000;
|
||||
const FUTURE_ABSOLUTE_IMPORT = 0x40000;
|
||||
const FUTURE_WITH_STATEMENT = 0x80000;
|
||||
const FUTURE_PRINT_FUNCTION = 0x100000;
|
||||
const FUTURE_UNICODE_LITERALS = 0x200000;
|
||||
const FUTURE_BARRY_AS_BDFL = 0x400000;
|
||||
const FUTURE_GENERATOR_STOP = 0x800000;
|
||||
const FUTURE_ANNOTATIONS = 0x1000000;
|
||||
/// If a code object represents a function and has a docstring,
|
||||
/// this bit is set and the first item in co_consts is the docstring.
|
||||
|
||||
@@ -754,9 +754,8 @@ impl Opcode {
|
||||
/// Stack effect when the instruction takes its branch (jump=true).
|
||||
///
|
||||
/// CPython equivalent: `stack_effect(opcode, oparg, jump=True)`.
|
||||
/// For most instructions this equals the fallthrough effect.
|
||||
/// Override for instructions where branch and fallthrough differ
|
||||
/// (e.g. [`Self::ForIter`]: fallthrough = +1, branch = −1).
|
||||
/// Current opcode metadata has the same real-opcode stack effect
|
||||
/// for jump and fallthrough stack-depth calculation.
|
||||
#[must_use]
|
||||
pub fn stack_effect_jump(&self, oparg: u32) -> i32 {
|
||||
self.stack_effect(oparg)
|
||||
@@ -1415,6 +1414,26 @@ mod tests {
|
||||
assert!(!AnyInstruction::from(PseudoOpcode::Jump).has_const());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stack_effects_match_cpython_opcode_metadata() {
|
||||
assert_eq!(Opcode::ForIter.stack_effect_info(0).popped(), 1);
|
||||
assert_eq!(Opcode::ForIter.stack_effect_info(0).pushed(), 2);
|
||||
assert_eq!(Opcode::ForIter.stack_effect(0), 1);
|
||||
assert_eq!(Opcode::ForIter.stack_effect_jump(0), 1);
|
||||
|
||||
assert_eq!(Opcode::EndAsyncFor.stack_effect_info(0).popped(), 2);
|
||||
assert_eq!(Opcode::EndAsyncFor.stack_effect_info(0).pushed(), 0);
|
||||
assert_eq!(Opcode::PopJumpIfFalse.stack_effect(0), -1);
|
||||
assert_eq!(Opcode::PopJumpIfFalse.stack_effect_jump(0), -1);
|
||||
|
||||
assert_eq!(PseudoOpcode::SetupFinally.stack_effect_info(0).pushed(), 1);
|
||||
assert_eq!(PseudoOpcode::SetupFinally.stack_effect(0), 0);
|
||||
assert_eq!(PseudoOpcode::SetupFinally.stack_effect_jump(0), 1);
|
||||
assert_eq!(PseudoOpcode::SetupCleanup.stack_effect_info(0).pushed(), 2);
|
||||
assert_eq!(PseudoOpcode::SetupCleanup.stack_effect(0), 0);
|
||||
assert_eq!(PseudoOpcode::SetupCleanup.stack_effect_jump(0), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_fallthrough_flags_match_cpython_basicblock_nofallthrough() {
|
||||
assert!(Opcode::JumpForward.is_no_fallthrough());
|
||||
|
||||
@@ -777,19 +777,20 @@ oparg_enum!(
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct UnpackExArgs {
|
||||
pub before: u8,
|
||||
pub after: u8,
|
||||
pub after: u32,
|
||||
}
|
||||
|
||||
impl From<u32> for UnpackExArgs {
|
||||
fn from(value: u32) -> Self {
|
||||
let [before, after, ..] = value.to_le_bytes();
|
||||
let before = (value & 0xFF) as u8;
|
||||
let after = value >> 8;
|
||||
Self { before, after }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnpackExArgs> for u32 {
|
||||
fn from(value: UnpackExArgs) -> Self {
|
||||
Self::from_le_bytes([value.before, value.after, 0, 0])
|
||||
Self::from(value.before) | (value.after << 8)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5055
-150
File diff suppressed because it is too large.
Load diff
@@ -205,7 +205,7 @@ mod tests {
|
||||
let scope = vm.new_scope_with_builtins();
|
||||
let code_obj = vm
|
||||
.compile(source.trim(), Mode::Exec, FNAME)
|
||||
.map_err(|err| vm.new_syntax_error(&err, Some(source)))
|
||||
.map_err(|err| err.into_pyexception(vm, Some(source)))
|
||||
.unwrap();
|
||||
scope.globals.set_item("code", code_obj.into(), vm).unwrap();
|
||||
|
||||
@@ -228,7 +228,7 @@ output = re.sub(r'(<code object \w+ at )0x[0-9a-fA-F]+', r'\g<1>0xdeadbeef', tmp
|
||||
|
||||
let py_code_obj = vm
|
||||
.compile(py_source, Mode::Exec, FNAME)
|
||||
.map_err(|err| vm.new_syntax_error(&err, Some(py_source)))
|
||||
.map_err(|err| err.into_pyexception(vm, Some(py_source)))
|
||||
.unwrap();
|
||||
|
||||
vm.run_code_obj(py_code_obj, scope.clone()).unwrap();
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: crates/stdlib/src/_opcode.rs
|
||||
assertion_line: 318
|
||||
expression: "dis(r#\"\ndef f(one: int):\n int.new_attr: int\n [list][0].new_attr: [int, str]\n my_lst = [1]\n my_lst[one]: int\n return my_lst\n\"#)"
|
||||
---
|
||||
0 RESUME 0
|
||||
@@ -15,7 +16,7 @@ expression: "dis(r#\"\ndef f(one: int):\n int.new_attr: int\n [list][0].ne
|
||||
|
||||
Disassembly of <code object __annotate__ at 0xdeadbeef file "<?>", line 1>:
|
||||
1 RESUME 0
|
||||
LOAD_FAST_BORROW 0 (format)
|
||||
LOAD_FAST_CHECK 0 (format)
|
||||
LOAD_SMALL_INT 2
|
||||
COMPARE_OP 132 (>)
|
||||
POP_JUMP_IF_FALSE 3 (to L1)
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/stdlib/src/_opcode.rs
|
||||
expression: x = not True
|
||||
assertion_line: 281
|
||||
expression: "dis(r#\"\nx = not True\n\"#)"
|
||||
---
|
||||
0 RESUME 0
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/stdlib/src/_opcode.rs
|
||||
expression: "if 1:\n pass"
|
||||
assertion_line: 290
|
||||
expression: "dis(r#\"\nif 1:\n pass\n\"#)"
|
||||
---
|
||||
0 RESUME 0
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/stdlib/src/_opcode.rs
|
||||
expression: "if True and False and False:\n pass"
|
||||
assertion_line: 252
|
||||
expression: "dis(r#\"\nif True and False and False:\n pass\n\"#)"
|
||||
---
|
||||
0 RESUME 0
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/stdlib/src/_opcode.rs
|
||||
expression: "if (True and False) or (False and True):\n pass"
|
||||
assertion_line: 262
|
||||
expression: "dis(r#\"\nif (True and False) or (False and True):\n pass\n\"#)"
|
||||
---
|
||||
0 RESUME 0
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/stdlib/src/_opcode.rs
|
||||
expression: "if True or False or False:\n pass"
|
||||
assertion_line: 242
|
||||
expression: "dis(r#\"\nif True or False or False:\n pass\n\"#)"
|
||||
---
|
||||
0 RESUME 0
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/stdlib/src/_opcode.rs
|
||||
expression: x = Test() and False or False
|
||||
assertion_line: 272
|
||||
expression: "dis(r#\"\nx = Test() and False or False\n\"#)"
|
||||
---
|
||||
0 RESUME 0
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ static_assertions = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
thin-vec = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
|
||||
flamer = { workspace = true, optional = true }
|
||||
|
||||
@@ -34,7 +34,7 @@ use core::{
|
||||
ops::Deref,
|
||||
pin::Pin,
|
||||
ptr::NonNull,
|
||||
sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering},
|
||||
sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, Ordering},
|
||||
};
|
||||
use indexmap::{IndexMap, map::Entry};
|
||||
use itertools::Itertools;
|
||||
@@ -53,6 +53,7 @@ pub struct PyType {
|
||||
pub heaptype_ext: Option<Pin<Box<HeapTypeExt>>>,
|
||||
/// Type version tag for inline caching. 0 means unassigned/invalidated.
|
||||
pub tp_version_tag: AtomicU32,
|
||||
pub abc_tpflags: AtomicU64,
|
||||
}
|
||||
|
||||
/// Monotonic counter for type version tags. Once it reaches `u32::MAX`,
|
||||
@@ -590,7 +591,9 @@ impl PyType {
|
||||
|
||||
// Check each base in order and inherit the first collection flag found
|
||||
for base in bases {
|
||||
let base_flags = base.slots.flags & COLLECTION_FLAGS;
|
||||
let base_flags = (base.slots.flags
|
||||
| PyTypeFlags::from_bits_truncate(base.abc_tpflags.load(Ordering::Acquire)))
|
||||
& COLLECTION_FLAGS;
|
||||
if !base_flags.is_empty() {
|
||||
slots.flags |= base_flags;
|
||||
return;
|
||||
@@ -598,6 +601,58 @@ impl PyType {
|
||||
}
|
||||
}
|
||||
|
||||
fn inherited_abc_tpflags(bases: &[PyRef<Self>]) -> u64 {
|
||||
const COLLECTION_FLAGS: PyTypeFlags = PyTypeFlags::from_bits_truncate(
|
||||
PyTypeFlags::SEQUENCE.bits() | PyTypeFlags::MAPPING.bits(),
|
||||
);
|
||||
for base in bases {
|
||||
let base_flags =
|
||||
PyTypeFlags::from_bits_truncate(base.abc_tpflags.load(Ordering::Acquire))
|
||||
& COLLECTION_FLAGS;
|
||||
if !base_flags.is_empty() {
|
||||
return base_flags.bits();
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
pub fn has_patma_collection_flag(&self, flag: PyTypeFlags) -> bool {
|
||||
debug_assert!(matches!(flag, PyTypeFlags::SEQUENCE | PyTypeFlags::MAPPING));
|
||||
const COLLECTION_FLAGS: PyTypeFlags = PyTypeFlags::from_bits_truncate(
|
||||
PyTypeFlags::SEQUENCE.bits() | PyTypeFlags::MAPPING.bits(),
|
||||
);
|
||||
let slot_flags = self.slots.flags & COLLECTION_FLAGS;
|
||||
if !slot_flags.is_empty() {
|
||||
return slot_flags.contains(flag);
|
||||
}
|
||||
PyTypeFlags::from_bits_truncate(self.abc_tpflags.load(Ordering::Acquire)).contains(flag)
|
||||
}
|
||||
|
||||
pub fn set_abc_collection_flags_recursive(&self, flags: PyTypeFlags) {
|
||||
const COLLECTION_FLAGS: PyTypeFlags = PyTypeFlags::from_bits_truncate(
|
||||
PyTypeFlags::SEQUENCE.bits() | PyTypeFlags::MAPPING.bits(),
|
||||
);
|
||||
let flags = flags & COLLECTION_FLAGS;
|
||||
if flags.is_empty() {
|
||||
return;
|
||||
}
|
||||
let collection_bits = COLLECTION_FLAGS.bits();
|
||||
let flags_bits = flags.bits();
|
||||
let _ = self
|
||||
.abc_tpflags
|
||||
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |old| {
|
||||
Some((old & !collection_bits) | flags_bits)
|
||||
});
|
||||
self.modified();
|
||||
for weak_ref in self.subclasses.read().iter() {
|
||||
if let Some(subclass) = weak_ref.upgrade()
|
||||
&& let Some(subclass) = subclass.downcast_ref::<Self>()
|
||||
{
|
||||
subclass.set_abc_collection_flags_recursive(flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check for __abc_tpflags__ and set the appropriate flags
|
||||
/// This checks in attrs and all base classes for __abc_tpflags__
|
||||
fn check_abc_tpflags(
|
||||
@@ -626,21 +681,19 @@ impl PyType {
|
||||
.to_owned(),
|
||||
);
|
||||
}
|
||||
// Don't override flags already inherited from a base class.
|
||||
if !slots.flags.intersects(COLLECTION_FLAGS) {
|
||||
slots.flags |= masked;
|
||||
}
|
||||
slots.flags.remove(COLLECTION_FLAGS);
|
||||
slots.flags |= masked;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// No __abc_tpflags__ on this class — inheritance already happened
|
||||
// in inherit_patma_flags, so nothing more to do if those bits are set.
|
||||
// No __abc_tpflags__ on this class. Inheritance already happened in
|
||||
// inherit_patma_flags, using base order and including ABC markers.
|
||||
if slots.flags.intersects(COLLECTION_FLAGS) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Then check in base classes (legacy path for cases that bypass
|
||||
// inherit_patma_flags).
|
||||
// Then check in base classes for legacy paths that bypassed
|
||||
// inherit_patma_flags.
|
||||
for base in bases {
|
||||
if let Some(abc_tpflags_obj) = base.find_name_in_mro(abc_tpflags_name)
|
||||
&& let Some(int_obj) = abc_tpflags_obj.downcast_ref::<crate::builtins::int::PyInt>()
|
||||
@@ -654,6 +707,7 @@ impl PyType {
|
||||
.to_owned(),
|
||||
);
|
||||
}
|
||||
slots.flags.remove(COLLECTION_FLAGS);
|
||||
slots.flags |= masked;
|
||||
return Ok(());
|
||||
}
|
||||
@@ -716,6 +770,7 @@ impl PyType {
|
||||
));
|
||||
}
|
||||
|
||||
let inherited_abc_tpflags = Self::inherited_abc_tpflags(&bases);
|
||||
let new_type = PyRef::new_ref(
|
||||
Self {
|
||||
base: Some(base),
|
||||
@@ -726,6 +781,7 @@ impl PyType {
|
||||
slots,
|
||||
heaptype_ext: Some(Pin::new(Box::new(heaptype_ext))),
|
||||
tp_version_tag: AtomicU32::new(0),
|
||||
abc_tpflags: AtomicU64::new(inherited_abc_tpflags),
|
||||
},
|
||||
metaclass,
|
||||
None,
|
||||
@@ -775,6 +831,7 @@ impl PyType {
|
||||
slots.flags |= PyTypeFlags::MANAGED_WEAKREF;
|
||||
}
|
||||
|
||||
let inherited_abc_tpflags = Self::inherited_abc_tpflags(core::slice::from_ref(&base));
|
||||
let bases = PyRwLock::new(vec![base.clone()]);
|
||||
let mro = base.mro_map_collect(|x| x.to_owned());
|
||||
|
||||
@@ -788,6 +845,7 @@ impl PyType {
|
||||
slots,
|
||||
heaptype_ext: None,
|
||||
tp_version_tag: AtomicU32::new(0),
|
||||
abc_tpflags: AtomicU64::new(inherited_abc_tpflags),
|
||||
},
|
||||
metaclass,
|
||||
None,
|
||||
|
||||
@@ -6,7 +6,7 @@ pub fn eval(vm: &VirtualMachine, source: &str, scope: Scope, source_path: &str)
|
||||
debug!("Code object: {bytecode:?}");
|
||||
vm.run_code_obj(bytecode, scope)
|
||||
}
|
||||
Err(err) => Err(vm.new_syntax_error(&err, Some(source))),
|
||||
Err(err) => Err(err.into_pyexception(vm, Some(source))),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2480,12 +2480,18 @@ pub(super) mod types {
|
||||
let maybe_lineno = zelf
|
||||
.as_object()
|
||||
.get_attr("lineno", vm)
|
||||
.and_then(|obj| obj.str_utf8(vm))
|
||||
.ok();
|
||||
let maybe_filename = zelf.as_object().get_attr("filename", vm).ok().map(|obj| {
|
||||
obj.str(vm)
|
||||
.unwrap_or_else(|_| vm.ctx.new_str("<filename str() failed>"))
|
||||
});
|
||||
.ok()
|
||||
.filter(|obj| !vm.is_none(obj))
|
||||
.and_then(|obj| obj.str_utf8(vm).ok());
|
||||
let maybe_filename = zelf
|
||||
.as_object()
|
||||
.get_attr("filename", vm)
|
||||
.ok()
|
||||
.filter(|obj| !vm.is_none(obj))
|
||||
.map(|obj| {
|
||||
obj.str(vm)
|
||||
.unwrap_or_else(|_| vm.ctx.new_str("<filename str() failed>"))
|
||||
});
|
||||
|
||||
let msg = match zelf.as_object().get_attr("msg", vm) {
|
||||
Ok(obj) => obj
|
||||
|
||||
+69
-23
@@ -3050,9 +3050,17 @@ impl ExecutingFrame<'_> {
|
||||
let subject = self.pop_value();
|
||||
let nargs_val = nargs.get(arg) as usize;
|
||||
|
||||
let Some(cls_type) = cls.downcast_ref::<PyType>() else {
|
||||
return Err(vm.new_type_error("called match pattern must be a class"));
|
||||
};
|
||||
// Only the error paths need the class name; compute it lazily so a
|
||||
// successful match does not take the name lock or allocate.
|
||||
let type_name = || cls_type.name().to_string();
|
||||
|
||||
// Check if subject is an instance of cls
|
||||
if subject.is_instance(cls.as_ref(), vm)? {
|
||||
let mut extracted = vec![];
|
||||
let seen_attrs = PySet::default().into_ref(&vm.ctx);
|
||||
|
||||
// Get __match_args__ for positional arguments if nargs > 0
|
||||
if nargs_val > 0 {
|
||||
@@ -3066,12 +3074,7 @@ impl ExecutingFrame<'_> {
|
||||
Ok(tuple) => tuple,
|
||||
Err(match_args) => {
|
||||
// __match_args__ must be a tuple
|
||||
// Get type names for error message
|
||||
let type_name = cls
|
||||
.downcast::<crate::builtins::PyType>()
|
||||
.ok()
|
||||
.and_then(|t| t.__name__(vm).to_str().map(str::to_owned))
|
||||
.unwrap_or_else(|| String::from("?"));
|
||||
let type_name = type_name();
|
||||
let match_args_type_name = match_args.class().__name__(vm);
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{type_name}.__match_args__ must be a tuple (got {match_args_type_name})"
|
||||
@@ -3081,9 +3084,12 @@ impl ExecutingFrame<'_> {
|
||||
|
||||
// Check if we have enough match args
|
||||
if match_args.len() < nargs_val {
|
||||
let type_name = type_name();
|
||||
let plural = if match_args.len() == 1 { "" } else { "s" };
|
||||
return Err(vm.new_type_error(format!(
|
||||
"class pattern accepts at most {} positional sub-patterns ({} given)",
|
||||
"{type_name}() accepts {} positional sub-pattern{} ({} given)",
|
||||
match_args.len(),
|
||||
plural,
|
||||
nargs_val
|
||||
)));
|
||||
}
|
||||
@@ -3094,11 +3100,20 @@ impl ExecutingFrame<'_> {
|
||||
let attr_name_str = match attr_name.downcast_ref::<PyStr>() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return Err(vm.new_type_error(
|
||||
"__match_args__ elements must be strings",
|
||||
));
|
||||
let attr_type_name = attr_name.class().name();
|
||||
return Err(vm.new_type_error(format!(
|
||||
"__match_args__ elements must be strings (got {attr_type_name})"
|
||||
)));
|
||||
}
|
||||
};
|
||||
if seen_attrs.__contains__(attr_name.as_object(), vm)? {
|
||||
let type_name = type_name();
|
||||
let attr_repr = attr_name.as_object().repr(vm)?;
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{type_name}() got multiple sub-patterns for attribute {attr_repr}"
|
||||
)));
|
||||
}
|
||||
seen_attrs.add(attr_name.clone(), vm)?;
|
||||
match subject.get_attr(attr_name_str, vm) {
|
||||
Ok(value) => extracted.push(value),
|
||||
Err(e)
|
||||
@@ -3115,9 +3130,8 @@ impl ExecutingFrame<'_> {
|
||||
// No __match_args__, check if this is a type with MATCH_SELF behavior
|
||||
// For built-in types like bool, int, str, list, tuple, dict, etc.
|
||||
// they match the subject itself as the single positional argument
|
||||
let is_match_self_type = cls
|
||||
.downcast::<PyType>()
|
||||
.is_ok_and(|t| t.slots.flags.contains(PyTypeFlags::_MATCH_SELF));
|
||||
let is_match_self_type =
|
||||
cls_type.slots.flags.contains(PyTypeFlags::_MATCH_SELF);
|
||||
|
||||
if is_match_self_type {
|
||||
if nargs_val == 1 {
|
||||
@@ -3125,16 +3139,18 @@ impl ExecutingFrame<'_> {
|
||||
extracted.push(subject.clone());
|
||||
} else if nargs_val > 1 {
|
||||
// Too many positional arguments for MATCH_SELF
|
||||
return Err(vm.new_type_error(
|
||||
"class pattern accepts at most 1 positional sub-pattern for MATCH_SELF types",
|
||||
));
|
||||
let type_name = type_name();
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{type_name}() accepts 1 positional sub-pattern ({nargs_val} given)"
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
// No __match_args__ and not a MATCH_SELF type
|
||||
if nargs_val > 0 {
|
||||
return Err(vm.new_type_error(
|
||||
"class pattern defines no positional sub-patterns (__match_args__ missing)",
|
||||
));
|
||||
let type_name = type_name();
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{type_name}() accepts 0 positional sub-patterns ({nargs_val} given)"
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3143,6 +3159,14 @@ impl ExecutingFrame<'_> {
|
||||
// Extract keyword attributes
|
||||
for name in kwd_attrs {
|
||||
let name_str = name.downcast_ref::<PyStr>().unwrap();
|
||||
if seen_attrs.__contains__(name_str.as_object(), vm)? {
|
||||
let type_name = type_name();
|
||||
let attr_repr = name.as_object().repr(vm)?;
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{type_name}() got multiple sub-patterns for attribute {attr_repr}"
|
||||
)));
|
||||
}
|
||||
seen_attrs.add(name.clone(), vm)?;
|
||||
match subject.get_attr(name_str, vm) {
|
||||
Ok(value) => extracted.push(value),
|
||||
Err(e) if e.fast_isinstance(vm.ctx.exceptions.attribute_error) => {
|
||||
@@ -3166,10 +3190,14 @@ impl ExecutingFrame<'_> {
|
||||
let subject = self.nth_value(1); // stack[-2]
|
||||
|
||||
// Check if subject is a mapping and extract values for keys
|
||||
if subject.class().slots.flags.contains(PyTypeFlags::MAPPING) {
|
||||
if subject
|
||||
.class()
|
||||
.has_patma_collection_flag(PyTypeFlags::MAPPING)
|
||||
{
|
||||
let keys = keys_tuple.downcast_ref::<PyTuple>().unwrap();
|
||||
let mut values = Vec::new();
|
||||
let mut all_match = true;
|
||||
let seen_keys = PySet::default().into_ref(&vm.ctx);
|
||||
|
||||
// We use the two argument form of map.get(key, default) for two reasons:
|
||||
// - Atomically check for a key and get its value without error handling.
|
||||
@@ -3186,6 +3214,13 @@ impl ExecutingFrame<'_> {
|
||||
.new_base_object(vm.ctx.types.object_type.to_owned(), None);
|
||||
|
||||
for key in keys {
|
||||
if seen_keys.__contains__(key.as_object(), vm)? {
|
||||
return Err(vm.new_value_error(format!(
|
||||
"mapping pattern checks duplicate key ({})",
|
||||
key.as_object().repr(vm)?
|
||||
)));
|
||||
}
|
||||
seen_keys.add(key.as_object().to_owned(), vm)?;
|
||||
// value = map.get(key, dummy)
|
||||
match get_method.call((key.as_object(), dummy.clone()), vm) {
|
||||
Ok(value) => {
|
||||
@@ -3202,6 +3237,13 @@ impl ExecutingFrame<'_> {
|
||||
} else {
|
||||
// Fallback if .get() method is not available (shouldn't happen for mappings)
|
||||
for key in keys {
|
||||
if seen_keys.__contains__(key.as_object(), vm)? {
|
||||
return Err(vm.new_value_error(format!(
|
||||
"mapping pattern checks duplicate key ({})",
|
||||
key.as_object().repr(vm)?
|
||||
)));
|
||||
}
|
||||
seen_keys.add(key.as_object().to_owned(), vm)?;
|
||||
match subject.get_item(key.as_object(), vm) {
|
||||
Ok(value) => values.push(value),
|
||||
Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => {
|
||||
@@ -3231,7 +3273,9 @@ impl ExecutingFrame<'_> {
|
||||
let subject = self.pop_value();
|
||||
|
||||
// Check if the type has the MAPPING flag
|
||||
let is_mapping = subject.class().slots.flags.contains(PyTypeFlags::MAPPING);
|
||||
let is_mapping = subject
|
||||
.class()
|
||||
.has_patma_collection_flag(PyTypeFlags::MAPPING);
|
||||
|
||||
self.push_value(subject);
|
||||
self.push_value(vm.ctx.new_bool(is_mapping).into());
|
||||
@@ -3242,7 +3286,9 @@ impl ExecutingFrame<'_> {
|
||||
let subject = self.pop_value();
|
||||
|
||||
// Check if the type has the SEQUENCE flag
|
||||
let is_sequence = subject.class().slots.flags.contains(PyTypeFlags::SEQUENCE);
|
||||
let is_sequence = subject
|
||||
.class()
|
||||
.has_patma_collection_flag(PyTypeFlags::SEQUENCE);
|
||||
|
||||
self.push_value(subject);
|
||||
self.push_value(vm.ctx.new_bool(is_sequence).into());
|
||||
@@ -6845,7 +6891,7 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_unpack_ex(&mut self, vm: &VirtualMachine, before: u8, after: u8) -> FrameResult {
|
||||
fn execute_unpack_ex(&mut self, vm: &VirtualMachine, before: u8, after: u32) -> FrameResult {
|
||||
let (before, after) = (before as usize, after as usize);
|
||||
let value = self.pop_value();
|
||||
let not_iterable = value.class().slots.iter.load().is_none()
|
||||
|
||||
@@ -141,7 +141,7 @@ pub fn import_file(
|
||||
file_path,
|
||||
vm.compile_opts(),
|
||||
)
|
||||
.map_err(|err| vm.new_syntax_error(&err, Some(content)))?;
|
||||
.map_err(|err| err.into_pyexception(vm, Some(content)))?;
|
||||
import_code_obj(vm, module_name, code, true)
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ pub fn import_source(vm: &VirtualMachine, module_name: &str, content: &str) -> P
|
||||
"<source>",
|
||||
vm.compile_opts(),
|
||||
)
|
||||
.map_err(|err| vm.new_syntax_error(&err, Some(content)))?;
|
||||
.map_err(|err| err.into_pyexception(vm, Some(content)))?;
|
||||
import_code_obj(vm, module_name, code, false)
|
||||
}
|
||||
|
||||
|
||||
@@ -2479,6 +2479,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
|
||||
slots: PyType::make_slots(),
|
||||
heaptype_ext: None,
|
||||
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
|
||||
abc_tpflags: core::sync::atomic::AtomicU64::new(0),
|
||||
};
|
||||
let object_payload = PyType {
|
||||
base: None,
|
||||
@@ -2489,6 +2490,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
|
||||
slots: object::PyBaseObject::make_slots(),
|
||||
heaptype_ext: None,
|
||||
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
|
||||
abc_tpflags: core::sync::atomic::AtomicU64::new(0),
|
||||
};
|
||||
// Both type_type and object_type are instances of `type`, which has
|
||||
// HAS_DICT and HAS_WEAKREF, so they need both ObjExt and WeakRefList prefixes.
|
||||
@@ -2585,6 +2587,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
|
||||
slots: PyWeak::make_slots(),
|
||||
heaptype_ext: None,
|
||||
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
|
||||
abc_tpflags: core::sync::atomic::AtomicU64::new(0),
|
||||
};
|
||||
let weakref_type = PyRef::new_ref(weakref_type, type_type.clone(), None);
|
||||
// Static type: untrack from GC (was tracked by new_ref because PyType has HAS_TRAVERSE)
|
||||
|
||||
@@ -207,7 +207,7 @@ impl VirtualMachine {
|
||||
event: TraceEvent,
|
||||
arg: Option<PyObjectRef>,
|
||||
) -> PyResult<Option<PyObjectRef>> {
|
||||
if self.use_tracing.get() {
|
||||
if self.use_tracing.get() && !self.tracing_is_suppressed() {
|
||||
self._trace_event_inner(event, arg)
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -247,7 +247,9 @@ impl VirtualMachine {
|
||||
// tracing function itself.
|
||||
if is_trace_event && !self.is_none(&trace_func) {
|
||||
self.use_tracing.set(false);
|
||||
self.enter_tracing();
|
||||
let res = trace_func.call(args.clone(), self);
|
||||
self.leave_tracing();
|
||||
self.use_tracing.set(true);
|
||||
match res {
|
||||
Ok(result) => {
|
||||
@@ -268,7 +270,9 @@ impl VirtualMachine {
|
||||
|
||||
if is_profile_event && !self.is_none(&profile_func) {
|
||||
self.use_tracing.set(false);
|
||||
self.enter_tracing();
|
||||
let res = profile_func.call(args, self);
|
||||
self.leave_tracing();
|
||||
self.use_tracing.set(true);
|
||||
if res.is_err() {
|
||||
*self.profile_func.borrow_mut() = self.ctx.none();
|
||||
|
||||
@@ -9,11 +9,11 @@ pub(crate) use _abc::module_def;
|
||||
mod _abc {
|
||||
use crate::{
|
||||
AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
builtins::{PyFrozenSet, PyList, PySet, PyStr, PyTupleRef, PyTypeRef, PyWeak},
|
||||
builtins::{PyFrozenSet, PyList, PySet, PyStr, PyTupleRef, PyType, PyTypeRef, PyWeak},
|
||||
common::lock::PyRwLock,
|
||||
convert::ToPyObject,
|
||||
protocol::PyIterReturn,
|
||||
types::Constructor,
|
||||
types::{Constructor, PyTypeFlags},
|
||||
};
|
||||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
@@ -238,6 +238,23 @@ mod _abc {
|
||||
// Invalidate negative cache
|
||||
increment_invalidation_counter();
|
||||
|
||||
if let Some(cls_type) = cls.downcast_ref::<PyType>()
|
||||
&& let Some(subclass_type) = subclass.downcast_ref::<PyType>()
|
||||
{
|
||||
// _abc_register propagates Py_TPFLAGS_SEQUENCE/MAPPING
|
||||
// recursively so MATCH_SEQUENCE/MATCH_MAPPING see ABC registration.
|
||||
let collection_mask = PyTypeFlags::SEQUENCE | PyTypeFlags::MAPPING;
|
||||
let collection_flags = (cls_type.slots.flags
|
||||
| PyTypeFlags::from_bits_truncate(cls_type.abc_tpflags.load(Ordering::Acquire)))
|
||||
& collection_mask;
|
||||
if !subclass_type.is(vm.ctx.types.str_type)
|
||||
&& !subclass_type.is(vm.ctx.types.bytes_type)
|
||||
&& !subclass_type.is(vm.ctx.types.bytearray_type)
|
||||
{
|
||||
subclass_type.set_abc_collection_flags_recursive(collection_flags);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(subclass)
|
||||
}
|
||||
|
||||
|
||||
+1800
-152
File diff suppressed because it is too large.
Load diff
@@ -2,14 +2,78 @@ use super::*;
|
||||
use rustpython_compiler_core::SourceFile;
|
||||
|
||||
pub(super) struct PositionalArguments {
|
||||
pub range: TextRange,
|
||||
pub args: Box<[ast::Expr]>,
|
||||
range: TextRange,
|
||||
kind: PositionalArgumentsKind,
|
||||
}
|
||||
|
||||
enum PositionalArgumentsKind {
|
||||
Args(Box<[ast::Expr]>),
|
||||
RuntimeValues(Vec<Option<ast::Expr>>),
|
||||
}
|
||||
|
||||
impl PositionalArguments {
|
||||
pub(super) fn ast_from_field(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: &PyObject,
|
||||
field: &'static str,
|
||||
typ: &str,
|
||||
) -> PyResult<Self> {
|
||||
let values: Vec<Option<ast::Expr>> =
|
||||
get_node_list_field(vm, source_file, object, field, typ)?;
|
||||
Ok(Self::from_values(TextRange::default(), values))
|
||||
}
|
||||
|
||||
fn from_args(range: TextRange, args: Box<[ast::Expr]>) -> Self {
|
||||
Self {
|
||||
range,
|
||||
kind: PositionalArgumentsKind::Args(args),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_values(range: TextRange, values: Vec<Option<ast::Expr>>) -> Self {
|
||||
Self {
|
||||
range,
|
||||
kind: PositionalArgumentsKind::RuntimeValues(values),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_values(range: TextRange, values: Vec<Option<ast::Expr>>) -> Self {
|
||||
if values.iter().any(Option::is_none) {
|
||||
Self::from_runtime_values(range, values)
|
||||
} else {
|
||||
Self::from_args(
|
||||
range,
|
||||
values
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn range(&self) -> TextRange {
|
||||
self.range
|
||||
}
|
||||
|
||||
fn into_args_and_runtime_values(self) -> (Box<[ast::Expr]>, Option<Vec<Option<ast::Expr>>>) {
|
||||
match self.kind {
|
||||
PositionalArgumentsKind::Args(args) => (args, None),
|
||||
PositionalArgumentsKind::RuntimeValues(values) => (
|
||||
lower_runtime_expr_list(values.clone()).into_boxed_slice(),
|
||||
Some(values),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for PositionalArguments {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self { args, range: _ } = self;
|
||||
BoxedSlice(args).ast_to_object(vm, source_file)
|
||||
match self.kind {
|
||||
PositionalArgumentsKind::Args(args) => BoxedSlice(args).ast_to_object(vm, source_file),
|
||||
PositionalArgumentsKind::RuntimeValues(values) => values.ast_to_object(vm, source_file),
|
||||
}
|
||||
}
|
||||
|
||||
fn ast_from_object(
|
||||
@@ -18,10 +82,7 @@ impl Node for PositionalArguments {
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let args: BoxedSlice<_> = Node::ast_from_object(vm, source_file, object)?;
|
||||
Ok(Self {
|
||||
args: args.0,
|
||||
range: TextRange::default(), // TODO
|
||||
})
|
||||
Ok(Self::from_args(TextRange::default(), args.0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +91,21 @@ pub(super) struct KeywordArguments {
|
||||
pub keywords: Box<[ast::Keyword]>,
|
||||
}
|
||||
|
||||
impl KeywordArguments {
|
||||
pub(super) fn ast_from_field(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: &PyObject,
|
||||
field: &'static str,
|
||||
typ: &str,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
keywords: get_node_boxed_slice_field(vm, source_file, object, field, typ)?,
|
||||
range: TextRange::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for KeywordArguments {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self { keywords, range: _ } = self;
|
||||
@@ -45,7 +121,7 @@ impl Node for KeywordArguments {
|
||||
let keywords: BoxedSlice<_> = Node::ast_from_object(vm, source_file, object)?;
|
||||
Ok(Self {
|
||||
keywords: keywords.0,
|
||||
range: TextRange::default(), // TODO
|
||||
range: TextRange::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -54,13 +130,16 @@ pub(super) fn merge_function_call_arguments(
|
||||
pos_args: PositionalArguments,
|
||||
key_args: KeywordArguments,
|
||||
) -> ast::Arguments {
|
||||
let range = pos_args.range.cover(key_args.range);
|
||||
let range = pos_args.range().cover(key_args.range);
|
||||
let (args, runtime_args) = pos_args.into_args_and_runtime_values();
|
||||
|
||||
ast::Arguments {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
args: pos_args.args,
|
||||
args,
|
||||
keywords: key_args.keywords,
|
||||
runtime_args,
|
||||
runtime_bases: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,10 +147,12 @@ pub(super) fn split_function_call_arguments(
|
||||
args: ast::Arguments,
|
||||
) -> (PositionalArguments, KeywordArguments) {
|
||||
let ast::Arguments {
|
||||
node_index: _,
|
||||
range: _,
|
||||
args,
|
||||
keywords,
|
||||
runtime_args,
|
||||
runtime_bases: _,
|
||||
..
|
||||
} = args;
|
||||
|
||||
let positional_arguments_range = args
|
||||
@@ -80,10 +161,10 @@ pub(super) fn split_function_call_arguments(
|
||||
.reduce(|acc, next| acc.cover(next))
|
||||
.unwrap_or_default();
|
||||
// debug_assert!(range.contains_range(positional_arguments_range));
|
||||
let positional_arguments = PositionalArguments {
|
||||
range: positional_arguments_range,
|
||||
args,
|
||||
};
|
||||
let positional_arguments = runtime_args.map_or_else(
|
||||
|| PositionalArguments::from_args(positional_arguments_range, args),
|
||||
|values| PositionalArguments::from_runtime_values(positional_arguments_range, values),
|
||||
);
|
||||
|
||||
let keyword_arguments_range = keywords
|
||||
.iter()
|
||||
@@ -107,10 +188,12 @@ pub(super) fn split_class_def_args(
|
||||
Some(args) => *args,
|
||||
};
|
||||
let ast::Arguments {
|
||||
node_index: _,
|
||||
range: _,
|
||||
args,
|
||||
keywords,
|
||||
runtime_args: _,
|
||||
runtime_bases,
|
||||
..
|
||||
} = args;
|
||||
|
||||
let positional_arguments_range = args
|
||||
@@ -119,10 +202,10 @@ pub(super) fn split_class_def_args(
|
||||
.reduce(|acc, next| acc.cover(next))
|
||||
.unwrap_or_default();
|
||||
// debug_assert!(range.contains_range(positional_arguments_range));
|
||||
let positional_arguments = PositionalArguments {
|
||||
range: positional_arguments_range,
|
||||
args,
|
||||
};
|
||||
let positional_arguments = runtime_bases.map_or_else(
|
||||
|| PositionalArguments::from_args(positional_arguments_range, args),
|
||||
|values| PositionalArguments::from_runtime_values(positional_arguments_range, values),
|
||||
);
|
||||
|
||||
let keyword_arguments_range = keywords
|
||||
.iter()
|
||||
@@ -146,10 +229,10 @@ pub(super) fn merge_class_def_args(
|
||||
return None;
|
||||
}
|
||||
|
||||
let args = if let Some(positional_arguments) = positional_arguments {
|
||||
positional_arguments.args
|
||||
let (args, runtime_bases) = if let Some(positional_arguments) = positional_arguments {
|
||||
positional_arguments.into_args_and_runtime_values()
|
||||
} else {
|
||||
vec![].into_boxed_slice()
|
||||
(vec![].into_boxed_slice(), None)
|
||||
};
|
||||
let keywords = if let Some(keyword_arguments) = keyword_arguments {
|
||||
keyword_arguments.keywords
|
||||
@@ -162,5 +245,7 @@ pub(super) fn merge_class_def_args(
|
||||
range: Default::default(), // TODO
|
||||
args,
|
||||
keywords,
|
||||
runtime_args: None,
|
||||
runtime_bases,
|
||||
}))
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::builtins::PyIntRef;
|
||||
use rustpython_codegen::compile::ruff_int_to_bigint;
|
||||
use rustpython_compiler_core::SourceFile;
|
||||
|
||||
@@ -13,7 +14,11 @@ impl Node for ast::Identifier {
|
||||
_source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let py_str = PyUtf8StrRef::try_from_object(vm, object)?;
|
||||
if !object.class().is(vm.ctx.types.str_type) {
|
||||
return Err(vm.new_type_error("AST identifier must be of type str"));
|
||||
}
|
||||
let py_str = PyUtf8StrRef::try_from_object(vm, object)
|
||||
.map_err(|_| vm.new_type_error("AST identifier must be of type str"))?;
|
||||
Ok(Self::new(py_str.as_str(), TextRange::default()))
|
||||
}
|
||||
}
|
||||
@@ -28,7 +33,6 @@ impl Node for ast::Int {
|
||||
_source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
// FIXME: performance
|
||||
let value: PyIntRef = object.try_into_value(vm)?;
|
||||
let value = value.as_bigint().to_string();
|
||||
Ok(value.parse().unwrap())
|
||||
@@ -45,6 +49,6 @@ impl Node for bool {
|
||||
_source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
i32::try_from_object(vm, object).map(|i| i != 0)
|
||||
node_object_to_i32(vm, object).map(|i| i != 0)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
use super::*;
|
||||
use crate::builtins::{PyComplex, PyFrozenSet, PyTuple};
|
||||
use ast::str_prefix::StringLiteralPrefix;
|
||||
use rustpython_compiler_core::SourceFile;
|
||||
use rustpython_codegen::compile::ruff_int_to_bigint;
|
||||
use rustpython_compiler_core::{SourceFile, bytecode::ConstantData};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Constant {
|
||||
pub(super) range: TextRange,
|
||||
pub(super) value: ConstantLiteral,
|
||||
kind: Option<Box<str>>,
|
||||
invalid_type: Option<String>,
|
||||
}
|
||||
|
||||
impl Constant {
|
||||
@@ -19,6 +22,8 @@ impl Constant {
|
||||
Self {
|
||||
range,
|
||||
value: ConstantLiteral::Str { value, prefix },
|
||||
kind: None,
|
||||
invalid_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +31,8 @@ impl Constant {
|
||||
Self {
|
||||
range,
|
||||
value: ConstantLiteral::Int(value),
|
||||
kind: None,
|
||||
invalid_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +40,8 @@ impl Constant {
|
||||
Self {
|
||||
range,
|
||||
value: ConstantLiteral::Float(value),
|
||||
kind: None,
|
||||
invalid_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +49,8 @@ impl Constant {
|
||||
Self {
|
||||
range,
|
||||
value: ConstantLiteral::Complex { real, imag },
|
||||
kind: None,
|
||||
invalid_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +58,8 @@ impl Constant {
|
||||
Self {
|
||||
range,
|
||||
value: ConstantLiteral::Bytes(value),
|
||||
kind: None,
|
||||
invalid_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +67,8 @@ impl Constant {
|
||||
Self {
|
||||
range,
|
||||
value: ConstantLiteral::Bool(value),
|
||||
kind: None,
|
||||
invalid_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +76,8 @@ impl Constant {
|
||||
Self {
|
||||
range,
|
||||
value: ConstantLiteral::None,
|
||||
kind: None,
|
||||
invalid_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,15 +85,29 @@ impl Constant {
|
||||
Self {
|
||||
range,
|
||||
value: ConstantLiteral::Ellipsis,
|
||||
kind: None,
|
||||
invalid_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_expr(self) -> ast::Expr {
|
||||
constant_to_ruff_expr(self)
|
||||
let Self {
|
||||
range,
|
||||
value,
|
||||
kind,
|
||||
invalid_type,
|
||||
} = self;
|
||||
ast::Expr::Constant(ast::ExprConstant {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
value: constant_data_to_ast_constant_value(constant_literal_to_constant_data(&value)),
|
||||
kind: kind.or_else(|| constant_literal_kind(&value)),
|
||||
invalid_type: invalid_type.map(String::into_boxed_str),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum ConstantLiteral {
|
||||
None,
|
||||
Bool(bool),
|
||||
@@ -96,20 +127,361 @@ pub(crate) enum ConstantLiteral {
|
||||
Ellipsis,
|
||||
}
|
||||
|
||||
pub(super) fn invalid_constant_type(expr: &ast::Expr) -> Option<Box<str>> {
|
||||
match expr {
|
||||
ast::Expr::Constant(expr) => expr.invalid_type.clone(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn runtime_string_from_pyobject(
|
||||
vm: &VirtualMachine,
|
||||
object: PyObjectRef,
|
||||
) -> (Option<Box<str>>, Option<Vec<u8>>) {
|
||||
runtime_string_from_object(vm, object)
|
||||
}
|
||||
|
||||
pub(super) fn runtime_string_object(
|
||||
vm: &VirtualMachine,
|
||||
value: Option<Box<str>>,
|
||||
bytes: Option<Vec<u8>>,
|
||||
) -> Option<PyObjectRef> {
|
||||
runtime_string_to_object(vm, value, bytes)
|
||||
}
|
||||
|
||||
pub(super) fn expr_constant_to_object(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
expr: ast::ExprConstant,
|
||||
) -> PyObjectRef {
|
||||
let ast::ExprConstant {
|
||||
node_index: _,
|
||||
range,
|
||||
value,
|
||||
kind,
|
||||
invalid_type: _,
|
||||
} = expr;
|
||||
let constant = ast_constant_value_to_constant_data(value);
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodeExprConstant::static_type().to_owned())
|
||||
.unwrap();
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item("value", constant_data_to_object(vm, constant), vm)
|
||||
.unwrap();
|
||||
let kind = kind.map_or_else(|| vm.ctx.none(), |kind| vm.ctx.new_str(kind).into());
|
||||
dict.set_item("kind", kind, vm).unwrap();
|
||||
node_add_location(&dict, range, vm, source_file);
|
||||
node.into()
|
||||
}
|
||||
|
||||
pub(super) fn runtime_interpolation_object(
|
||||
vm: &VirtualMachine,
|
||||
str: Option<ast::ConstantValue>,
|
||||
format_spec: Option<Box<ast::Expr>>,
|
||||
) -> Option<(PyObjectRef, Option<Box<ast::Expr>>)> {
|
||||
let str = str?;
|
||||
Some((
|
||||
constant_data_to_object(vm, ast_constant_value_to_constant_data(str)),
|
||||
format_spec,
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) fn runtime_stmt_type_comment_object(
|
||||
vm: &VirtualMachine,
|
||||
value: Option<Box<str>>,
|
||||
bytes: Option<Vec<u8>>,
|
||||
) -> Option<PyObjectRef> {
|
||||
runtime_string_object(vm, value, bytes)
|
||||
}
|
||||
|
||||
fn constant_literal_to_constant_data(value: &ConstantLiteral) -> ConstantData {
|
||||
match value {
|
||||
ConstantLiteral::None => ConstantData::None,
|
||||
ConstantLiteral::Bool(value) => ConstantData::Boolean { value: *value },
|
||||
ConstantLiteral::Str { value, .. } => ConstantData::Str {
|
||||
value: value.as_ref().into(),
|
||||
},
|
||||
ConstantLiteral::Bytes(value) => ConstantData::Bytes {
|
||||
value: value.to_vec(),
|
||||
},
|
||||
ConstantLiteral::Int(value) => ConstantData::Integer {
|
||||
value: ruff_int_to_bigint(value).unwrap(),
|
||||
},
|
||||
ConstantLiteral::Tuple(value) => ConstantData::Tuple {
|
||||
elements: value
|
||||
.iter()
|
||||
.map(constant_literal_to_constant_data)
|
||||
.collect(),
|
||||
},
|
||||
ConstantLiteral::FrozenSet(value) => ConstantData::Frozenset {
|
||||
elements: value
|
||||
.iter()
|
||||
.map(constant_literal_to_constant_data)
|
||||
.collect(),
|
||||
},
|
||||
ConstantLiteral::Float(value) => ConstantData::Float { value: *value },
|
||||
ConstantLiteral::Complex { real, imag } => ConstantData::Complex {
|
||||
value: num_complex::Complex::new(*real, *imag),
|
||||
},
|
||||
ConstantLiteral::Ellipsis => ConstantData::Ellipsis,
|
||||
}
|
||||
}
|
||||
|
||||
fn constant_literal_kind(value: &ConstantLiteral) -> Option<Box<str>> {
|
||||
match value {
|
||||
ConstantLiteral::Str {
|
||||
prefix: StringLiteralPrefix::Unicode,
|
||||
..
|
||||
} => Some("u".into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn constant_data_to_ast_constant_value(value: ConstantData) -> ast::ConstantValue {
|
||||
match value {
|
||||
ConstantData::None => ast::ConstantValue::None,
|
||||
ConstantData::Boolean { value } => ast::ConstantValue::Boolean(value),
|
||||
ConstantData::Str { value } => ast::ConstantValue::Str(value.to_string().into_boxed_str()),
|
||||
ConstantData::Bytes { value } => ast::ConstantValue::Bytes(value.into_boxed_slice()),
|
||||
ConstantData::Integer { value } => ast::ConstantValue::Integer(value.to_string().into()),
|
||||
ConstantData::Tuple { elements } => ast::ConstantValue::Tuple(
|
||||
elements
|
||||
.into_iter()
|
||||
.map(constant_data_to_ast_constant_value)
|
||||
.collect(),
|
||||
),
|
||||
ConstantData::Frozenset { elements } => ast::ConstantValue::Frozenset(
|
||||
elements
|
||||
.into_iter()
|
||||
.map(constant_data_to_ast_constant_value)
|
||||
.collect(),
|
||||
),
|
||||
ConstantData::Float { value } => ast::ConstantValue::Float(value),
|
||||
ConstantData::Complex { value } => ast::ConstantValue::Complex {
|
||||
real: value.re,
|
||||
imag: value.im,
|
||||
},
|
||||
ConstantData::Ellipsis => ast::ConstantValue::Ellipsis,
|
||||
ConstantData::Code { .. } | ConstantData::Slice { .. } => {
|
||||
unreachable!("ast.Constant values cannot contain code objects or slices")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn ast_constant_value_to_constant_data(value: ast::ConstantValue) -> ConstantData {
|
||||
match value {
|
||||
ast::ConstantValue::None => ConstantData::None,
|
||||
ast::ConstantValue::Boolean(value) => ConstantData::Boolean { value },
|
||||
ast::ConstantValue::Str(value) => ConstantData::Str {
|
||||
value: value.to_string().into(),
|
||||
},
|
||||
ast::ConstantValue::Bytes(value) => ConstantData::Bytes {
|
||||
value: value.into_vec(),
|
||||
},
|
||||
ast::ConstantValue::Integer(value) => ConstantData::Integer {
|
||||
value: value
|
||||
.parse()
|
||||
.expect("RustPython ast.Constant integer values are decimal integers"),
|
||||
},
|
||||
ast::ConstantValue::Tuple(elements) => ConstantData::Tuple {
|
||||
elements: elements
|
||||
.into_iter()
|
||||
.map(ast_constant_value_to_constant_data)
|
||||
.collect(),
|
||||
},
|
||||
ast::ConstantValue::Frozenset(elements) => ConstantData::Frozenset {
|
||||
elements: elements
|
||||
.into_iter()
|
||||
.map(ast_constant_value_to_constant_data)
|
||||
.collect(),
|
||||
},
|
||||
ast::ConstantValue::Float(value) => ConstantData::Float { value },
|
||||
ast::ConstantValue::Complex { real, imag } => ConstantData::Complex {
|
||||
value: num_complex::Complex::new(real, imag),
|
||||
},
|
||||
ast::ConstantValue::Ellipsis => ConstantData::Ellipsis,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn constant_object_to_constant_data(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
value_object: PyObjectRef,
|
||||
) -> PyResult<ConstantData> {
|
||||
let value = ConstantLiteral::ast_from_object(vm, source_file, value_object)?;
|
||||
Ok(constant_literal_to_constant_data(&value))
|
||||
}
|
||||
|
||||
fn runtime_string_from_object(
|
||||
vm: &VirtualMachine,
|
||||
object: PyObjectRef,
|
||||
) -> (Option<Box<str>>, Option<Vec<u8>>) {
|
||||
if object.class().is(vm.ctx.types.str_type) {
|
||||
(
|
||||
Some(
|
||||
object
|
||||
.try_to_value::<String>(vm)
|
||||
.expect("AST string field was validated as str")
|
||||
.into_boxed_str(),
|
||||
),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
None,
|
||||
Some(
|
||||
object
|
||||
.try_to_value::<Vec<u8>>(vm)
|
||||
.expect("AST string field was validated as bytes"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime_string_to_object(
|
||||
vm: &VirtualMachine,
|
||||
value: Option<Box<str>>,
|
||||
bytes: Option<Vec<u8>>,
|
||||
) -> Option<PyObjectRef> {
|
||||
if let Some(bytes) = bytes {
|
||||
Some(vm.ctx.new_bytes(bytes).into())
|
||||
} else {
|
||||
value.map(|value| vm.ctx.new_str(value).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn first_invalid_constant_type(vm: &VirtualMachine, value_object: PyObjectRef) -> PyResult<String> {
|
||||
let cls = value_object.class();
|
||||
let class_name = cls.name().to_owned();
|
||||
if cls.is(vm.ctx.types.tuple_type) {
|
||||
vm.with_recursion(" during compilation", || {
|
||||
let tuple = value_object.clone().downcast::<PyTuple>().map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"Expected type {}, not {}",
|
||||
PyTuple::static_type().name(),
|
||||
obj.class().name()
|
||||
))
|
||||
})?;
|
||||
for item in tuple.iter() {
|
||||
if let Some(invalid_type) = first_invalid_constant_type_opt(vm, item.clone())? {
|
||||
return Ok(invalid_type);
|
||||
}
|
||||
}
|
||||
Ok(class_name)
|
||||
})
|
||||
} else if cls.is(vm.ctx.types.frozenset_type) {
|
||||
vm.with_recursion(" during compilation", || {
|
||||
let set = value_object.clone().downcast::<PyFrozenSet>().unwrap();
|
||||
for item in set.elements() {
|
||||
if let Some(invalid_type) = first_invalid_constant_type_opt(vm, item)? {
|
||||
return Ok(invalid_type);
|
||||
}
|
||||
}
|
||||
Ok(class_name)
|
||||
})
|
||||
} else {
|
||||
Ok(class_name)
|
||||
}
|
||||
}
|
||||
|
||||
fn first_invalid_constant_type_opt(
|
||||
vm: &VirtualMachine,
|
||||
value_object: PyObjectRef,
|
||||
) -> PyResult<Option<String>> {
|
||||
let cls = value_object.class();
|
||||
if cls.is(vm.ctx.types.none_type)
|
||||
|| cls.is(vm.ctx.types.bool_type)
|
||||
|| cls.is(vm.ctx.types.str_type)
|
||||
|| cls.is(vm.ctx.types.bytes_type)
|
||||
|| cls.is(vm.ctx.types.int_type)
|
||||
|| cls.is(vm.ctx.types.float_type)
|
||||
|| cls.is(vm.ctx.types.complex_type)
|
||||
|| cls.is(vm.ctx.types.ellipsis_type)
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
if cls.is(vm.ctx.types.tuple_type) || cls.is(vm.ctx.types.frozenset_type) {
|
||||
return first_invalid_constant_type(vm, value_object).map(Some);
|
||||
}
|
||||
Ok(Some(cls.name().to_owned()))
|
||||
}
|
||||
|
||||
fn constant_data_to_object(vm: &VirtualMachine, constant: ConstantData) -> PyObjectRef {
|
||||
match constant {
|
||||
ConstantData::None => vm.ctx.none(),
|
||||
ConstantData::Boolean { value } => vm.ctx.new_bool(value).to_pyobject(vm),
|
||||
ConstantData::Str { value } => vm.ctx.new_str(value.to_string()).to_pyobject(vm),
|
||||
ConstantData::Bytes { value } => vm.ctx.new_bytes(value).to_pyobject(vm),
|
||||
ConstantData::Integer { value } => vm.ctx.new_int(value).into(),
|
||||
ConstantData::Tuple { elements } => {
|
||||
let value = elements
|
||||
.into_iter()
|
||||
.map(|c| constant_data_to_object(vm, c))
|
||||
.collect();
|
||||
vm.ctx.new_tuple(value).to_pyobject(vm)
|
||||
}
|
||||
ConstantData::Frozenset { elements } => PyFrozenSet::from_iter(
|
||||
vm,
|
||||
elements.into_iter().map(|c| constant_data_to_object(vm, c)),
|
||||
)
|
||||
.unwrap()
|
||||
.into_pyobject(vm),
|
||||
ConstantData::Float { value } => vm.ctx.new_float(value).into_pyobject(vm),
|
||||
ConstantData::Complex { value } => vm.ctx.new_complex(value).into_pyobject(vm),
|
||||
ConstantData::Ellipsis => vm.ctx.ellipsis.clone().into(),
|
||||
ConstantData::Code { .. } | ConstantData::Slice { .. } => {
|
||||
unreachable!("ast.Constant values cannot contain code objects or slices")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
pub(super) fn constant_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<Constant> {
|
||||
let value_object = get_node_field(vm, &object, "value", "Constant")?;
|
||||
let (value, invalid_type) =
|
||||
match ConstantLiteral::ast_from_object(vm, source_file, value_object.clone()) {
|
||||
Ok(value) => (value, None),
|
||||
Err(_) => (
|
||||
ConstantLiteral::None,
|
||||
Some(first_invalid_constant_type(vm, value_object)?),
|
||||
),
|
||||
};
|
||||
let kind = get_node_field_opt(vm, &object, "kind")?
|
||||
.map(|object| {
|
||||
if !object.class().is(vm.ctx.types.str_type) {
|
||||
return Err(vm.new_type_error("AST string must be of type str"));
|
||||
}
|
||||
Ok(object.try_to_value::<String>(vm)?.into_boxed_str())
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
Ok(Constant {
|
||||
range,
|
||||
value,
|
||||
kind,
|
||||
invalid_type,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for Constant {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self { range, value } = self;
|
||||
let Self {
|
||||
range,
|
||||
value,
|
||||
kind,
|
||||
invalid_type: _,
|
||||
} = self;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodeExprConstant::static_type().to_owned())
|
||||
.unwrap();
|
||||
let kind = match &value {
|
||||
ConstantLiteral::Str {
|
||||
prefix: StringLiteralPrefix::Unicode,
|
||||
..
|
||||
} => vm.ctx.new_str("u").into(),
|
||||
_ => vm.ctx.none(),
|
||||
};
|
||||
let kind = kind
|
||||
.or_else(|| constant_literal_kind(&value))
|
||||
.map_or_else(|| vm.ctx.none(), |kind| vm.ctx.new_str(kind).into());
|
||||
let value = value.ast_to_object(vm, source_file);
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item("value", value, vm).unwrap();
|
||||
@@ -123,16 +495,8 @@ impl Node for Constant {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let value_object = get_node_field(vm, &object, "value", "Constant")?;
|
||||
let value = Node::ast_from_object(vm, source_file, value_object)?;
|
||||
|
||||
Ok(Self {
|
||||
value,
|
||||
// kind: get_node_field_opt(_vm, &_object, "kind")?
|
||||
// .map(|obj| Node::ast_from_object(_vm, obj))
|
||||
// .transpose()?,
|
||||
range: range_from_object(vm, source_file, object, "Constant")?,
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "Constant")?;
|
||||
constant_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,8 +565,12 @@ impl Node for ConstantLiteral {
|
||||
})?;
|
||||
let tuple = tuple
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.map(|object| Node::ast_from_object(vm, source_file, object))
|
||||
.map(|object| {
|
||||
let object = object.clone();
|
||||
vm.with_recursion(" during compilation", || {
|
||||
Node::ast_from_object(vm, source_file, object)
|
||||
})
|
||||
})
|
||||
.collect::<PyResult<_>>()?;
|
||||
Self::Tuple(tuple)
|
||||
} else if cls.is(vm.ctx.types.frozenset_type) {
|
||||
@@ -210,7 +578,11 @@ impl Node for ConstantLiteral {
|
||||
let elements = set
|
||||
.elements()
|
||||
.into_iter()
|
||||
.map(|object| Node::ast_from_object(vm, source_file, object))
|
||||
.map(|object| {
|
||||
vm.with_recursion(" during compilation", || {
|
||||
Node::ast_from_object(vm, source_file, object)
|
||||
})
|
||||
})
|
||||
.collect::<PyResult<_>>()?;
|
||||
Self::FrozenSet(elements)
|
||||
} else if cls.is(vm.ctx.types.float_type) {
|
||||
@@ -244,117 +616,6 @@ impl Node for ConstantLiteral {
|
||||
}
|
||||
}
|
||||
|
||||
fn constant_to_ruff_expr(value: Constant) -> ast::Expr {
|
||||
let Constant { value, range } = value;
|
||||
match value {
|
||||
ConstantLiteral::None => ast::Expr::NoneLiteral(ast::ExprNoneLiteral {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
}),
|
||||
ConstantLiteral::Bool(value) => ast::Expr::BooleanLiteral(ast::ExprBooleanLiteral {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
value,
|
||||
}),
|
||||
ConstantLiteral::Str { value, prefix } => {
|
||||
ast::Expr::StringLiteral(ast::ExprStringLiteral {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
value: ast::StringLiteralValue::single(ast::StringLiteral {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
value,
|
||||
flags: ast::StringLiteralFlags::empty().with_prefix(prefix),
|
||||
}),
|
||||
})
|
||||
}
|
||||
ConstantLiteral::Bytes(value) => {
|
||||
ast::Expr::BytesLiteral(ast::ExprBytesLiteral {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
value: ast::BytesLiteralValue::single(ast::BytesLiteral {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
value,
|
||||
flags: ast::BytesLiteralFlags::empty(), // TODO
|
||||
}),
|
||||
})
|
||||
}
|
||||
ConstantLiteral::Int(value) => ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
value: ast::Number::Int(value),
|
||||
}),
|
||||
ConstantLiteral::Tuple(value) => ast::Expr::Tuple(ast::ExprTuple {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
elts: value
|
||||
.into_iter()
|
||||
.map(|value| {
|
||||
constant_to_ruff_expr(Constant {
|
||||
range: TextRange::default(),
|
||||
value,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
// TODO: Does this matter?
|
||||
parenthesized: true,
|
||||
}),
|
||||
ConstantLiteral::FrozenSet(value) => {
|
||||
let args = if value.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
vec![ast::Expr::Set(ast::ExprSet {
|
||||
node_index: Default::default(),
|
||||
range: TextRange::default(),
|
||||
elts: value
|
||||
.into_iter()
|
||||
.map(|value| {
|
||||
constant_to_ruff_expr(Constant {
|
||||
range: TextRange::default(),
|
||||
value,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
})]
|
||||
};
|
||||
ast::Expr::Call(ast::ExprCall {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
func: Box::new(ast::Expr::Name(ast::ExprName {
|
||||
node_index: Default::default(),
|
||||
range: TextRange::default(),
|
||||
id: ast::name::Name::new_static("frozenset"),
|
||||
ctx: ast::ExprContext::Load,
|
||||
})),
|
||||
arguments: ast::Arguments {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
args: args.into(),
|
||||
keywords: Box::default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
ConstantLiteral::Float(value) => ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
value: ast::Number::Float(value),
|
||||
}),
|
||||
ConstantLiteral::Complex { real, imag } => {
|
||||
ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
value: ast::Number::Complex { real, imag },
|
||||
})
|
||||
}
|
||||
ConstantLiteral::Ellipsis => ast::Expr::EllipsisLiteral(ast::ExprEllipsisLiteral {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn number_literal_to_object(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
@@ -364,6 +625,7 @@ pub(super) fn number_literal_to_object(
|
||||
node_index: _,
|
||||
range,
|
||||
value,
|
||||
..
|
||||
} = constant;
|
||||
let c = match value {
|
||||
ast::Number::Int(n) => Constant::new_int(n, range),
|
||||
@@ -382,6 +644,7 @@ pub(super) fn string_literal_to_object(
|
||||
node_index: _,
|
||||
range,
|
||||
value,
|
||||
..
|
||||
} = constant;
|
||||
let prefix = value
|
||||
.iter()
|
||||
@@ -400,6 +663,7 @@ pub(super) fn bytes_literal_to_object(
|
||||
node_index: _,
|
||||
range,
|
||||
value,
|
||||
..
|
||||
} = constant;
|
||||
let bytes = value.as_slice().iter().flat_map(|b| b.value.iter());
|
||||
let c = Constant::new_bytes(bytes.copied().collect(), range);
|
||||
@@ -415,6 +679,7 @@ pub(super) fn boolean_literal_to_object(
|
||||
node_index: _,
|
||||
range,
|
||||
value,
|
||||
..
|
||||
} = constant;
|
||||
let c = Constant::new_bool(value, range);
|
||||
c.ast_to_object(vm, source_file)
|
||||
@@ -428,6 +693,7 @@ pub(super) fn none_literal_to_object(
|
||||
let ast::ExprNoneLiteral {
|
||||
node_index: _,
|
||||
range,
|
||||
..
|
||||
} = constant;
|
||||
let c = Constant::new_none(range);
|
||||
c.ast_to_object(vm, source_file)
|
||||
@@ -441,6 +707,7 @@ pub(super) fn ellipsis_literal_to_object(
|
||||
let ast::ExprEllipsisLiteral {
|
||||
node_index: _,
|
||||
range,
|
||||
..
|
||||
} = constant;
|
||||
let c = Constant::new_ellipsis(range);
|
||||
c.ast_to_object(vm, source_file)
|
||||
|
||||
@@ -12,10 +12,15 @@ pub(super) fn ast_to_object(
|
||||
range,
|
||||
test,
|
||||
body,
|
||||
runtime_body,
|
||||
runtime_orelse,
|
||||
} = clause;
|
||||
let Some(test) = test else {
|
||||
assert!(rest.len() == 0);
|
||||
return body.ast_to_object(vm, source_file);
|
||||
return runtime_body.map_or_else(
|
||||
|| body.ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
);
|
||||
};
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodeStmtIf::static_type().to_owned())
|
||||
@@ -24,10 +29,15 @@ pub(super) fn ast_to_object(
|
||||
|
||||
dict.set_item("test", test.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
dict.set_item("body", body.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
let body = runtime_body.map_or_else(
|
||||
|| body.ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
);
|
||||
dict.set_item("body", body, vm).unwrap();
|
||||
|
||||
let orelse = if let Some(next) = rest.next() {
|
||||
let orelse = if let Some(values) = runtime_orelse {
|
||||
values.ast_to_object(vm, source_file)
|
||||
} else if let Some(next) = rest.next() {
|
||||
if next.test.is_some() {
|
||||
let next = ast::ElifElseClause {
|
||||
range: TextRange::new(next.range.start(), range.end()),
|
||||
@@ -37,7 +47,10 @@ pub(super) fn ast_to_object(
|
||||
.new_list(vec![ast_to_object(next, rest, vm, source_file)])
|
||||
.into()
|
||||
} else {
|
||||
next.body.ast_to_object(vm, source_file)
|
||||
next.runtime_body.map_or_else(
|
||||
|| next.body.ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
vm.ctx.new_list(vec![]).into()
|
||||
@@ -48,40 +61,45 @@ pub(super) fn ast_to_object(
|
||||
node.into()
|
||||
}
|
||||
|
||||
pub(super) fn ast_from_object(
|
||||
pub(super) fn ast_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<ast::StmtIf> {
|
||||
let test = Node::ast_from_object(vm, source_file, get_node_field(vm, &object, "test", "If")?)?;
|
||||
let body = Node::ast_from_object(vm, source_file, get_node_field(vm, &object, "body", "If")?)?;
|
||||
let orelse: Vec<ast::Stmt> = Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "orelse", "If")?,
|
||||
)?;
|
||||
let range = range_from_object(vm, source_file, object, "If")?;
|
||||
let test = get_required_node_field(vm, source_file, &object, "test", "If")?;
|
||||
let body: Vec<Option<ast::Stmt>> = get_node_list_field(vm, source_file, &object, "body", "If")?;
|
||||
let orelse: Vec<Option<ast::Stmt>> =
|
||||
get_node_list_field(vm, source_file, &object, "orelse", "If")?;
|
||||
let runtime_body = runtime_stmt_list_metadata(&body);
|
||||
let runtime_orelse = runtime_stmt_list_metadata(&orelse);
|
||||
let body = lower_runtime_stmt_list(body);
|
||||
let orelse = lower_runtime_stmt_list(orelse);
|
||||
|
||||
let elif_else_clauses = if orelse.is_empty() {
|
||||
vec![]
|
||||
} else if let [ast::Stmt::If(_)] = &*orelse {
|
||||
let Some(ast::Stmt::If(ast::StmtIf {
|
||||
node_index: _,
|
||||
node_index,
|
||||
range,
|
||||
test,
|
||||
body,
|
||||
mut elif_else_clauses,
|
||||
runtime_body,
|
||||
})) = orelse.into_iter().next()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
debug_assert!(runtime_orelse.is_none());
|
||||
elif_else_clauses.insert(
|
||||
0,
|
||||
ast::ElifElseClause {
|
||||
node_index: Default::default(),
|
||||
node_index,
|
||||
range,
|
||||
test: Some(*test),
|
||||
body,
|
||||
runtime_body,
|
||||
runtime_orelse: None,
|
||||
},
|
||||
);
|
||||
elif_else_clauses
|
||||
@@ -91,6 +109,8 @@ pub(super) fn ast_from_object(
|
||||
range,
|
||||
test: None,
|
||||
body: orelse,
|
||||
runtime_body: runtime_orelse,
|
||||
runtime_orelse: None,
|
||||
}]
|
||||
};
|
||||
|
||||
@@ -100,5 +120,6 @@ pub(super) fn ast_from_object(
|
||||
body,
|
||||
elif_else_clauses,
|
||||
range,
|
||||
runtime_body,
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,22 @@
|
||||
use super::*;
|
||||
use rustpython_compiler_core::SourceFile;
|
||||
|
||||
fn ensure_excepthandler_node(vm: &VirtualMachine, object: &PyObjectRef) -> PyResult<()> {
|
||||
if vm.is_none(object)
|
||||
|| !is_node_instance(
|
||||
vm,
|
||||
object,
|
||||
pyast::NodeExceptHandlerExceptHandler::static_type(),
|
||||
)?
|
||||
{
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of excepthandler, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// sum
|
||||
impl Node for ast::ExceptHandler {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
@@ -13,25 +29,53 @@ impl Node for ast::ExceptHandler {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let cls = object.class();
|
||||
Ok(
|
||||
if cls.is(pyast::NodeExceptHandlerExceptHandler::static_type()) {
|
||||
Self::ExceptHandler(ast::ExceptHandlerExceptHandler::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of excepthandler, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
},
|
||||
)
|
||||
ensure_excepthandler_node(vm, &object)?;
|
||||
let range = excepthandler_range_from_object(vm, source_file, object.clone())?;
|
||||
Ok(Self::ExceptHandler(except_handler_from_object_with_range(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
range,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
fn except_handler_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<ast::ExceptHandlerExceptHandler> {
|
||||
let body: Vec<Option<ast::Stmt>> =
|
||||
get_node_list_field(vm, source_file, &object, "body", "ExceptHandler")?;
|
||||
let (runtime_body, body) = runtime_stmt_list_from_values(body);
|
||||
Ok(ast::ExceptHandlerExceptHandler {
|
||||
node_index: Default::default(),
|
||||
type_: get_node_field_opt(vm, &object, "type")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
name: get_node_field_opt(vm, &object, "name")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
body,
|
||||
range,
|
||||
runtime_body,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn except_handler_from_object_unvalidated_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<ast::ExceptHandler> {
|
||||
ensure_excepthandler_node(vm, &object)?;
|
||||
let range = excepthandler_range_from_object_unvalidated(vm, source_file, object.clone())?;
|
||||
Ok(ast::ExceptHandler::ExceptHandler(
|
||||
except_handler_from_object_with_range(vm, source_file, object, range)?,
|
||||
))
|
||||
}
|
||||
|
||||
impl Node for ast::ExceptHandlerExceptHandler {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
@@ -40,6 +84,7 @@ impl Node for ast::ExceptHandlerExceptHandler {
|
||||
name,
|
||||
body,
|
||||
range,
|
||||
runtime_body,
|
||||
} = self;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(
|
||||
@@ -52,8 +97,11 @@ impl Node for ast::ExceptHandlerExceptHandler {
|
||||
.unwrap();
|
||||
dict.set_item("name", name.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
dict.set_item("body", body.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
let body = runtime_body.map_or_else(
|
||||
|| body.ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
);
|
||||
dict.set_item("body", body, vm).unwrap();
|
||||
node_add_location(&dict, range, vm, source_file);
|
||||
node.into()
|
||||
}
|
||||
@@ -63,20 +111,7 @@ impl Node for ast::ExceptHandlerExceptHandler {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
type_: get_node_field_opt(vm, &object, "type")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
name: get_node_field_opt(vm, &object, "name")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
body: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "body", "ExceptHandler")?,
|
||||
)?,
|
||||
range: range_from_object(vm, source_file, object, "ExceptHandler")?,
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "ExceptHandler")?;
|
||||
except_handler_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -18,7 +18,7 @@ use rustpython_compiler_core::SourceFile;
|
||||
/// - `FunctionType`: A function signature with argument and return type
|
||||
/// annotations, representing the type hints of a function (e.g., `def add(x: int, y: int) -> int`).
|
||||
pub(super) enum Mod {
|
||||
Module(ast::ModModule),
|
||||
Module(ModModule),
|
||||
Interactive(ModInteractive),
|
||||
Expression(ast::ModExpression),
|
||||
FunctionType(ModFunctionType),
|
||||
@@ -40,53 +40,66 @@ impl Node for Mod {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let cls = object.class();
|
||||
Ok(if cls.is(pyast::NodeModModule::static_type()) {
|
||||
Self::Module(ast::ModModule::ast_from_object(vm, source_file, object)?)
|
||||
} else if cls.is(pyast::NodeModInteractive::static_type()) {
|
||||
Self::Interactive(ModInteractive::ast_from_object(vm, source_file, object)?)
|
||||
} else if cls.is(pyast::NodeModExpression::static_type()) {
|
||||
Self::Expression(ast::ModExpression::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
} else if cls.is(pyast::NodeModFunctionType::static_type()) {
|
||||
Self::FunctionType(ModFunctionType::ast_from_object(vm, source_file, object)?)
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of mod, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
})
|
||||
Ok(
|
||||
if object.is_instance(pyast::NodeModModule::static_type().as_object(), vm)? {
|
||||
Self::Module(ModModule::ast_from_object(vm, source_file, object)?)
|
||||
} else if object
|
||||
.is_instance(pyast::NodeModInteractive::static_type().as_object(), vm)?
|
||||
{
|
||||
Self::Interactive(ModInteractive::ast_from_object(vm, source_file, object)?)
|
||||
} else if object.is_instance(pyast::NodeModExpression::static_type().as_object(), vm)? {
|
||||
Self::Expression(ast::ModExpression::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
} else if object
|
||||
.is_instance(pyast::NodeModFunctionType::static_type().as_object(), vm)?
|
||||
{
|
||||
Self::FunctionType(ModFunctionType::ast_from_object(vm, source_file, object)?)
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of mod, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct ModModule {
|
||||
pub(crate) module: ast::ModModule,
|
||||
pub(crate) type_ignores: Vec<TypeIgnore>,
|
||||
}
|
||||
|
||||
// constructor
|
||||
impl Node for ast::ModModule {
|
||||
impl Node for ModModule {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
module,
|
||||
type_ignores,
|
||||
} = self;
|
||||
let ast::ModModule {
|
||||
node_index: _,
|
||||
body,
|
||||
// type_ignores,
|
||||
range,
|
||||
} = self;
|
||||
range: _,
|
||||
runtime_body,
|
||||
} = module;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodeModModule::static_type().to_owned())
|
||||
.unwrap();
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item("body", body.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
// TODO: Improve ruff API
|
||||
// ruff ignores type_ignore comments currently.
|
||||
let type_ignores: Vec<TypeIgnore> = vec![];
|
||||
let body = runtime_body.map_or_else(
|
||||
|| body.ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
);
|
||||
dict.set_item("body", body, vm).unwrap();
|
||||
dict.set_item(
|
||||
"type_ignores",
|
||||
type_ignores.ast_to_object(vm, source_file),
|
||||
vm,
|
||||
)
|
||||
.unwrap();
|
||||
let _ = range;
|
||||
node.into()
|
||||
}
|
||||
|
||||
@@ -95,38 +108,45 @@ impl Node for ast::ModModule {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let body: Vec<Option<ast::Stmt>> =
|
||||
get_node_list_field(vm, source_file, &object, "body", "Module")?;
|
||||
let (runtime_body, body) = runtime_stmt_list_from_values(body);
|
||||
let type_ignores = get_node_list_field(vm, source_file, &object, "type_ignores", "Module")?;
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
body: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "body", "Module")?,
|
||||
)?,
|
||||
// type_ignores: Node::ast_from_object(
|
||||
// _vm,
|
||||
// get_node_field(_vm, &_object, "type_ignores", "Module")?,
|
||||
// )?,
|
||||
range: Default::default(),
|
||||
module: ast::ModModule {
|
||||
node_index: Default::default(),
|
||||
body,
|
||||
range: Default::default(),
|
||||
runtime_body,
|
||||
},
|
||||
type_ignores,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct ModInteractive {
|
||||
pub(crate) range: TextRange,
|
||||
pub(crate) body: Vec<ast::Stmt>,
|
||||
pub(crate) body: ast::Suite,
|
||||
pub(crate) runtime_body: Option<Vec<Option<ast::Stmt>>>,
|
||||
}
|
||||
|
||||
// constructor
|
||||
impl Node for ModInteractive {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self { body, range } = self;
|
||||
let Self {
|
||||
body,
|
||||
range: _,
|
||||
runtime_body,
|
||||
} = self;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodeModInteractive::static_type().to_owned())
|
||||
.unwrap();
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item("body", body.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
let _ = range;
|
||||
let body = runtime_body.map_or_else(
|
||||
|| body.ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
);
|
||||
dict.set_item("body", body, vm).unwrap();
|
||||
node.into()
|
||||
}
|
||||
|
||||
@@ -135,13 +155,13 @@ impl Node for ModInteractive {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let body: Vec<Option<ast::Stmt>> =
|
||||
get_node_list_field(vm, source_file, &object, "body", "Interactive")?;
|
||||
let (runtime_body, body) = runtime_stmt_list_from_values(body);
|
||||
Ok(Self {
|
||||
body: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "body", "Interactive")?,
|
||||
)?,
|
||||
body,
|
||||
range: Default::default(),
|
||||
runtime_body,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -152,7 +172,7 @@ impl Node for ast::ModExpression {
|
||||
let Self {
|
||||
node_index: _,
|
||||
body,
|
||||
range,
|
||||
range: _,
|
||||
} = self;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodeModExpression::static_type().to_owned())
|
||||
@@ -160,7 +180,6 @@ impl Node for ast::ModExpression {
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item("body", body.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
let _ = range;
|
||||
node.into()
|
||||
}
|
||||
|
||||
@@ -171,11 +190,7 @@ impl Node for ast::ModExpression {
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
body: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "body", "Expression")?,
|
||||
)?,
|
||||
body: get_required_node_field(vm, source_file, &object, "body", "Expression")?,
|
||||
range: Default::default(),
|
||||
})
|
||||
}
|
||||
@@ -184,7 +199,7 @@ impl Node for ast::ModExpression {
|
||||
pub(super) struct ModFunctionType {
|
||||
pub(crate) argtypes: Box<[ast::Expr]>,
|
||||
pub(crate) returns: ast::Expr,
|
||||
pub(crate) range: TextRange,
|
||||
pub(crate) runtime_argtypes: Option<Vec<Option<ast::Expr>>>,
|
||||
}
|
||||
|
||||
// constructor
|
||||
@@ -193,21 +208,19 @@ impl Node for ModFunctionType {
|
||||
let Self {
|
||||
argtypes,
|
||||
returns,
|
||||
range,
|
||||
runtime_argtypes,
|
||||
} = self;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodeModFunctionType::static_type().to_owned())
|
||||
.unwrap();
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item(
|
||||
"argtypes",
|
||||
BoxedSlice(argtypes).ast_to_object(vm, source_file),
|
||||
vm,
|
||||
)
|
||||
.unwrap();
|
||||
let argtypes = runtime_argtypes.map_or_else(
|
||||
|| BoxedSlice(argtypes).ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
);
|
||||
dict.set_item("argtypes", argtypes, vm).unwrap();
|
||||
dict.set_item("returns", returns.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
let _ = range;
|
||||
node.into()
|
||||
}
|
||||
|
||||
@@ -216,21 +229,13 @@ impl Node for ModFunctionType {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let argtypes: Vec<Option<ast::Expr>> =
|
||||
get_node_list_field(vm, source_file, &object, "argtypes", "FunctionType")?;
|
||||
let (runtime_argtypes, argtypes) = runtime_expr_list_from_values(argtypes);
|
||||
Ok(Self {
|
||||
argtypes: {
|
||||
let argtypes: BoxedSlice<_> = Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "argtypes", "FunctionType")?,
|
||||
)?;
|
||||
argtypes.0
|
||||
},
|
||||
returns: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "returns", "FunctionType")?,
|
||||
)?,
|
||||
range: Default::default(),
|
||||
argtypes: argtypes.into_boxed_slice(),
|
||||
returns: get_required_node_field(vm, source_file, &object, "returns", "FunctionType")?,
|
||||
runtime_argtypes,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{PyObjectRef, PyResult, VirtualMachine};
|
||||
use crate::{PyObjectRef, PyResult, VirtualMachine, builtins::PyList};
|
||||
use rustpython_compiler_core::SourceFile;
|
||||
use thin_vec::ThinVec;
|
||||
|
||||
pub(crate) trait Node: Sized {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef;
|
||||
@@ -31,14 +32,52 @@ impl<T: Node> Node for Vec<T> {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
// Recursion guard for each element: prevents stack overflow when a
|
||||
// sequence element transitively references the sequence itself
|
||||
// (e.g. `l = ast.List(...); l.elts = [l]`). See issue #4862.
|
||||
vm.extract_elements_with(&object, |obj| {
|
||||
vm.with_recursion("while traversing AST node", || {
|
||||
Node::ast_from_object(vm, source_file, obj)
|
||||
})
|
||||
})
|
||||
let list = object.downcast_ref::<PyList>().ok_or_else(|| {
|
||||
vm.new_type_error(format!(
|
||||
"AST list field must be a list, not a {}",
|
||||
object.class().name()
|
||||
))
|
||||
})?;
|
||||
let len = list.borrow_vec().len();
|
||||
let mut result = Self::with_capacity(len);
|
||||
for i in 0..len {
|
||||
let item = {
|
||||
let items = list.borrow_vec();
|
||||
if items.len() != len {
|
||||
return Err(
|
||||
vm.new_runtime_error("AST list field changed size during iteration")
|
||||
);
|
||||
}
|
||||
items[i].clone()
|
||||
};
|
||||
result.push(vm.with_recursion("while traversing AST node", || {
|
||||
Node::ast_from_object(vm, source_file, item)
|
||||
})?);
|
||||
if list.borrow_vec().len() != len {
|
||||
return Err(vm.new_runtime_error("AST list field changed size during iteration"));
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Node> Node for ThinVec<T> {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
vm.ctx
|
||||
.new_list(
|
||||
self.into_iter()
|
||||
.map(|node| node.ast_to_object(vm, source_file))
|
||||
.collect(),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn ast_from_object(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Vec::<T>::ast_from_object(vm, source_file, object).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,10 +91,6 @@ impl<T: Node> Node for Box<T> {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
// Recursion guard: every descent through a Box<AstNode> increments the
|
||||
// VM's recursion depth so cyclic or pathologically deep ASTs raise
|
||||
// RecursionError instead of overflowing the native stack.
|
||||
// See issue #4862.
|
||||
vm.with_recursion("while traversing AST node", || {
|
||||
T::ast_from_object(vm, source_file, object).map(Self::new)
|
||||
})
|
||||
|
||||
@@ -16,17 +16,18 @@ impl Node for ast::BoolOp {
|
||||
_source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let cls = object.class();
|
||||
Ok(if cls.is(pyast::NodeBoolOpAnd::static_type()) {
|
||||
Self::And
|
||||
} else if cls.is(pyast::NodeBoolOpOr::static_type()) {
|
||||
Self::Or
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of boolop, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
})
|
||||
Ok(
|
||||
if is_node_instance(vm, &object, pyast::NodeBoolOpAnd::static_type())? {
|
||||
Self::And
|
||||
} else if is_node_instance(vm, &object, pyast::NodeBoolOpOr::static_type())? {
|
||||
Self::Or
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of boolop, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,39 +57,40 @@ impl Node for ast::Operator {
|
||||
_source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let cls = object.class();
|
||||
Ok(if cls.is(pyast::NodeOperatorAdd::static_type()) {
|
||||
Self::Add
|
||||
} else if cls.is(pyast::NodeOperatorSub::static_type()) {
|
||||
Self::Sub
|
||||
} else if cls.is(pyast::NodeOperatorMult::static_type()) {
|
||||
Self::Mult
|
||||
} else if cls.is(pyast::NodeOperatorMatMult::static_type()) {
|
||||
Self::MatMult
|
||||
} else if cls.is(pyast::NodeOperatorDiv::static_type()) {
|
||||
Self::Div
|
||||
} else if cls.is(pyast::NodeOperatorMod::static_type()) {
|
||||
Self::Mod
|
||||
} else if cls.is(pyast::NodeOperatorPow::static_type()) {
|
||||
Self::Pow
|
||||
} else if cls.is(pyast::NodeOperatorLShift::static_type()) {
|
||||
Self::LShift
|
||||
} else if cls.is(pyast::NodeOperatorRShift::static_type()) {
|
||||
Self::RShift
|
||||
} else if cls.is(pyast::NodeOperatorBitOr::static_type()) {
|
||||
Self::BitOr
|
||||
} else if cls.is(pyast::NodeOperatorBitXor::static_type()) {
|
||||
Self::BitXor
|
||||
} else if cls.is(pyast::NodeOperatorBitAnd::static_type()) {
|
||||
Self::BitAnd
|
||||
} else if cls.is(pyast::NodeOperatorFloorDiv::static_type()) {
|
||||
Self::FloorDiv
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of operator, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
})
|
||||
Ok(
|
||||
if is_node_instance(vm, &object, pyast::NodeOperatorAdd::static_type())? {
|
||||
Self::Add
|
||||
} else if is_node_instance(vm, &object, pyast::NodeOperatorSub::static_type())? {
|
||||
Self::Sub
|
||||
} else if is_node_instance(vm, &object, pyast::NodeOperatorMult::static_type())? {
|
||||
Self::Mult
|
||||
} else if is_node_instance(vm, &object, pyast::NodeOperatorMatMult::static_type())? {
|
||||
Self::MatMult
|
||||
} else if is_node_instance(vm, &object, pyast::NodeOperatorDiv::static_type())? {
|
||||
Self::Div
|
||||
} else if is_node_instance(vm, &object, pyast::NodeOperatorMod::static_type())? {
|
||||
Self::Mod
|
||||
} else if is_node_instance(vm, &object, pyast::NodeOperatorPow::static_type())? {
|
||||
Self::Pow
|
||||
} else if is_node_instance(vm, &object, pyast::NodeOperatorLShift::static_type())? {
|
||||
Self::LShift
|
||||
} else if is_node_instance(vm, &object, pyast::NodeOperatorRShift::static_type())? {
|
||||
Self::RShift
|
||||
} else if is_node_instance(vm, &object, pyast::NodeOperatorBitOr::static_type())? {
|
||||
Self::BitOr
|
||||
} else if is_node_instance(vm, &object, pyast::NodeOperatorBitXor::static_type())? {
|
||||
Self::BitXor
|
||||
} else if is_node_instance(vm, &object, pyast::NodeOperatorBitAnd::static_type())? {
|
||||
Self::BitAnd
|
||||
} else if is_node_instance(vm, &object, pyast::NodeOperatorFloorDiv::static_type())? {
|
||||
Self::FloorDiv
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of operator, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,21 +111,22 @@ impl Node for ast::UnaryOp {
|
||||
_source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let cls = object.class();
|
||||
Ok(if cls.is(pyast::NodeUnaryOpInvert::static_type()) {
|
||||
Self::Invert
|
||||
} else if cls.is(pyast::NodeUnaryOpNot::static_type()) {
|
||||
Self::Not
|
||||
} else if cls.is(pyast::NodeUnaryOpUAdd::static_type()) {
|
||||
Self::UAdd
|
||||
} else if cls.is(pyast::NodeUnaryOpUSub::static_type()) {
|
||||
Self::USub
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of unaryop, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
})
|
||||
Ok(
|
||||
if is_node_instance(vm, &object, pyast::NodeUnaryOpInvert::static_type())? {
|
||||
Self::Invert
|
||||
} else if is_node_instance(vm, &object, pyast::NodeUnaryOpNot::static_type())? {
|
||||
Self::Not
|
||||
} else if is_node_instance(vm, &object, pyast::NodeUnaryOpUAdd::static_type())? {
|
||||
Self::UAdd
|
||||
} else if is_node_instance(vm, &object, pyast::NodeUnaryOpUSub::static_type())? {
|
||||
Self::USub
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of unaryop, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,32 +153,33 @@ impl Node for ast::CmpOp {
|
||||
_source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let cls = object.class();
|
||||
Ok(if cls.is(pyast::NodeCmpOpEq::static_type()) {
|
||||
Self::Eq
|
||||
} else if cls.is(pyast::NodeCmpOpNotEq::static_type()) {
|
||||
Self::NotEq
|
||||
} else if cls.is(pyast::NodeCmpOpLt::static_type()) {
|
||||
Self::Lt
|
||||
} else if cls.is(pyast::NodeCmpOpLtE::static_type()) {
|
||||
Self::LtE
|
||||
} else if cls.is(pyast::NodeCmpOpGt::static_type()) {
|
||||
Self::Gt
|
||||
} else if cls.is(pyast::NodeCmpOpGtE::static_type()) {
|
||||
Self::GtE
|
||||
} else if cls.is(pyast::NodeCmpOpIs::static_type()) {
|
||||
Self::Is
|
||||
} else if cls.is(pyast::NodeCmpOpIsNot::static_type()) {
|
||||
Self::IsNot
|
||||
} else if cls.is(pyast::NodeCmpOpIn::static_type()) {
|
||||
Self::In
|
||||
} else if cls.is(pyast::NodeCmpOpNotIn::static_type()) {
|
||||
Self::NotIn
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of cmpop, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
})
|
||||
Ok(
|
||||
if is_node_instance(vm, &object, pyast::NodeCmpOpEq::static_type())? {
|
||||
Self::Eq
|
||||
} else if is_node_instance(vm, &object, pyast::NodeCmpOpNotEq::static_type())? {
|
||||
Self::NotEq
|
||||
} else if is_node_instance(vm, &object, pyast::NodeCmpOpLt::static_type())? {
|
||||
Self::Lt
|
||||
} else if is_node_instance(vm, &object, pyast::NodeCmpOpLtE::static_type())? {
|
||||
Self::LtE
|
||||
} else if is_node_instance(vm, &object, pyast::NodeCmpOpGt::static_type())? {
|
||||
Self::Gt
|
||||
} else if is_node_instance(vm, &object, pyast::NodeCmpOpGtE::static_type())? {
|
||||
Self::GtE
|
||||
} else if is_node_instance(vm, &object, pyast::NodeCmpOpIs::static_type())? {
|
||||
Self::Is
|
||||
} else if is_node_instance(vm, &object, pyast::NodeCmpOpIsNot::static_type())? {
|
||||
Self::IsNot
|
||||
} else if is_node_instance(vm, &object, pyast::NodeCmpOpIn::static_type())? {
|
||||
Self::In
|
||||
} else if is_node_instance(vm, &object, pyast::NodeCmpOpNotIn::static_type())? {
|
||||
Self::NotIn
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of cmpop, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -11,14 +11,12 @@ impl Node for ast::ConversionFlag {
|
||||
_source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
// Python's AST uses ASCII codes: 's', 'r', 'a', -1=None
|
||||
// Note: 255 is -1i8 as u8 (ruff's ConversionFlag::None)
|
||||
match i32::try_from_object(vm, object)? {
|
||||
-1 | 255 => Ok(Self::None),
|
||||
match node_object_to_i32(vm, object)? {
|
||||
-1 => Ok(Self::None),
|
||||
x if x == b's' as i32 => Ok(Self::Str),
|
||||
x if x == b'r' as i32 => Ok(Self::Repr),
|
||||
x if x == b'a' as i32 => Ok(Self::Ascii),
|
||||
_ => Err(vm.new_value_error("invalid conversion flag")),
|
||||
x => Err(vm.new_system_error(format!("Unrecognized conversion character {x}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,10 +32,13 @@ impl Node for ast::name::Name {
|
||||
_source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
match object.downcast::<PyStr>() {
|
||||
Ok(name) => Ok(Self::new(name)),
|
||||
Err(_) => Err(vm.new_value_error("expected str for name")),
|
||||
if !object.class().is(vm.ctx.types.str_type) {
|
||||
return Err(vm.new_type_error("AST identifier must be of type str"));
|
||||
}
|
||||
object
|
||||
.downcast::<PyStr>()
|
||||
.map(Self::new)
|
||||
.map_err(|_| vm.new_type_error("AST identifier must be of type str"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,11 +90,7 @@ impl Node for ast::Alias {
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
name: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field_required(vm, &object, "name", "alias")?,
|
||||
)?,
|
||||
name: get_required_identifier_field(vm, source_file, &object, "name", "alias")?,
|
||||
asname: get_node_field_opt(vm, &object, "asname")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
@@ -137,10 +134,12 @@ impl Node for ast::WithItem {
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
context_expr: Node::ast_from_object(
|
||||
context_expr: get_required_node_field(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field_required(vm, &object, "context_expr", "withitem")?,
|
||||
&object,
|
||||
"context_expr",
|
||||
"withitem",
|
||||
)?,
|
||||
optional_vars: get_node_field_opt(vm, &object, "optional_vars")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
|
||||
@@ -11,10 +11,12 @@ impl Node for ast::Parameters {
|
||||
vararg,
|
||||
kwonlyargs,
|
||||
kwarg,
|
||||
range,
|
||||
range: _,
|
||||
runtime_defaults,
|
||||
} = self;
|
||||
let (posonlyargs, args, defaults) =
|
||||
let (posonlyargs, args, mut defaults) =
|
||||
extract_positional_parameter_defaults(posonlyargs, args);
|
||||
defaults.runtime_defaults = runtime_defaults;
|
||||
let (kwonlyargs, kw_defaults) = extract_keyword_parameter_defaults(kwonlyargs);
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodeArguments::static_type().to_owned())
|
||||
@@ -40,9 +42,12 @@ impl Node for ast::Parameters {
|
||||
.unwrap();
|
||||
dict.set_item("kwarg", kwarg.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
dict.set_item("defaults", defaults.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
let _ = range;
|
||||
let runtime_defaults = defaults.runtime_defaults.take();
|
||||
let defaults = runtime_defaults.map_or_else(
|
||||
|| defaults.ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
);
|
||||
dict.set_item("defaults", defaults, vm).unwrap();
|
||||
node.into()
|
||||
}
|
||||
|
||||
@@ -51,33 +56,44 @@ impl Node for ast::Parameters {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let kwonlyargs = Node::ast_from_object(
|
||||
let posonlyargs = PositionalParameters::ast_from_field(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "kwonlyargs", "arguments")?,
|
||||
&object,
|
||||
"posonlyargs",
|
||||
"arguments",
|
||||
)?;
|
||||
let kw_defaults = Node::ast_from_object(
|
||||
let args =
|
||||
PositionalParameters::ast_from_field(vm, source_file, &object, "args", "arguments")?;
|
||||
let vararg = get_node_field_opt(vm, &object, "vararg")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?;
|
||||
let kwonlyargs =
|
||||
KeywordParameters::ast_from_field(vm, source_file, &object, "kwonlyargs", "arguments")?;
|
||||
let kw_defaults = ParameterDefaults::ast_from_field(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "kw_defaults", "arguments")?,
|
||||
&object,
|
||||
"kw_defaults",
|
||||
"arguments",
|
||||
)?;
|
||||
let kwarg = get_node_field_opt(vm, &object, "kwarg")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?;
|
||||
let defaults = ParameterDefaults::ast_from_field_preserve_none(
|
||||
vm,
|
||||
source_file,
|
||||
&object,
|
||||
"defaults",
|
||||
"arguments",
|
||||
)?;
|
||||
let kwonlyargs = merge_keyword_parameter_defaults(vm, kwonlyargs, kw_defaults)?;
|
||||
|
||||
let posonlyargs = Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "posonlyargs", "arguments")?,
|
||||
)?;
|
||||
let args = Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "args", "arguments")?,
|
||||
)?;
|
||||
let defaults = Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "defaults", "arguments")?,
|
||||
)?;
|
||||
let ParameterDefaults {
|
||||
runtime_defaults,
|
||||
defaults,
|
||||
_range: _,
|
||||
} = defaults;
|
||||
let kwonlyargs = merge_keyword_parameter_defaults(vm, kwonlyargs, kw_defaults)?;
|
||||
let (posonlyargs, args) =
|
||||
merge_positional_parameter_defaults(vm, posonlyargs, args, defaults)?;
|
||||
|
||||
@@ -85,14 +101,11 @@ impl Node for ast::Parameters {
|
||||
node_index: Default::default(),
|
||||
posonlyargs,
|
||||
args,
|
||||
vararg: get_node_field_opt(vm, &object, "vararg")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
vararg,
|
||||
kwonlyargs,
|
||||
kwarg: get_node_field_opt(vm, &object, "kwarg")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
kwarg,
|
||||
range: Default::default(),
|
||||
runtime_defaults,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -103,62 +116,68 @@ impl Node for ast::Parameters {
|
||||
|
||||
// product
|
||||
impl Node for ast::Parameter {
|
||||
fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
node_index: _,
|
||||
name,
|
||||
annotation,
|
||||
// type_comment,
|
||||
range,
|
||||
runtime_type_comment,
|
||||
runtime_type_comment_bytes,
|
||||
} = self;
|
||||
|
||||
// ruff covers the ** in range but python expects it to start at the ident
|
||||
let range = TextRange::new(name.start(), range.end());
|
||||
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(_vm, pyast::NodeArg::static_type().to_owned())
|
||||
.into_ref_with_type(vm, pyast::NodeArg::static_type().to_owned())
|
||||
.unwrap();
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item("arg", name.ast_to_object(_vm, source_file), _vm)
|
||||
dict.set_item("arg", name.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
dict.set_item(
|
||||
"annotation",
|
||||
annotation.ast_to_object(_vm, source_file),
|
||||
_vm,
|
||||
dict.set_item("annotation", annotation.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
let type_comment = super::constant::runtime_string_object(
|
||||
vm,
|
||||
runtime_type_comment,
|
||||
runtime_type_comment_bytes,
|
||||
)
|
||||
.unwrap();
|
||||
// Ruff AST doesn't track type_comment, so always set to None
|
||||
dict.set_item("type_comment", _vm.ctx.none(), _vm).unwrap();
|
||||
node_add_location(&dict, range, _vm, source_file);
|
||||
.unwrap_or_else(|| vm.ctx.none());
|
||||
dict.set_item("type_comment", type_comment, vm).unwrap();
|
||||
node_add_location(&dict, range, vm, source_file);
|
||||
node.into()
|
||||
}
|
||||
|
||||
fn ast_from_object(
|
||||
_vm: &VirtualMachine,
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
_object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let name = get_required_identifier_field(vm, source_file, &_object, "arg", "arg")?;
|
||||
let annotation = get_node_field_opt(vm, &_object, "annotation")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?;
|
||||
let type_comment = get_ast_string_field_opt(vm, &_object, "type_comment")?;
|
||||
let (runtime_type_comment, runtime_type_comment_bytes) = type_comment
|
||||
.map_or((None, None), |type_comment| {
|
||||
super::constant::runtime_string_from_pyobject(vm, type_comment)
|
||||
});
|
||||
let range = range_from_object(vm, source_file, _object, "arg")?;
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
name: Node::ast_from_object(
|
||||
_vm,
|
||||
source_file,
|
||||
get_node_field_required(_vm, &_object, "arg", "arg")?,
|
||||
)?,
|
||||
annotation: get_node_field_opt(_vm, &_object, "annotation")?
|
||||
.map(|obj| Node::ast_from_object(_vm, source_file, obj))
|
||||
.transpose()?,
|
||||
// type_comment: get_node_field_opt(_vm, &_object, "type_comment")?
|
||||
// .map(|obj| Node::ast_from_object(_vm, obj))
|
||||
// .transpose()?,
|
||||
range: range_from_object(_vm, source_file, _object, "arg")?,
|
||||
name,
|
||||
annotation,
|
||||
range,
|
||||
runtime_type_comment,
|
||||
runtime_type_comment_bytes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// product
|
||||
impl Node for ast::Keyword {
|
||||
fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
node_index: _,
|
||||
arg,
|
||||
@@ -166,32 +185,28 @@ impl Node for ast::Keyword {
|
||||
range: _range,
|
||||
} = self;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(_vm, pyast::NodeKeyword::static_type().to_owned())
|
||||
.into_ref_with_type(vm, pyast::NodeKeyword::static_type().to_owned())
|
||||
.unwrap();
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item("arg", arg.ast_to_object(_vm, source_file), _vm)
|
||||
dict.set_item("arg", arg.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
dict.set_item("value", value.ast_to_object(_vm, source_file), _vm)
|
||||
dict.set_item("value", value.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
node_add_location(&dict, _range, _vm, source_file);
|
||||
node_add_location(&dict, _range, vm, source_file);
|
||||
node.into()
|
||||
}
|
||||
fn ast_from_object(
|
||||
_vm: &VirtualMachine,
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
_object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
arg: get_node_field_opt(_vm, &_object, "arg")?
|
||||
.map(|obj| Node::ast_from_object(_vm, source_file, obj))
|
||||
arg: get_node_field_opt(vm, &_object, "arg")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
value: Node::ast_from_object(
|
||||
_vm,
|
||||
source_file,
|
||||
get_node_field_required(_vm, &_object, "value", "keyword")?,
|
||||
)?,
|
||||
range: range_from_object(_vm, source_file, _object, "keyword")?,
|
||||
value: get_required_node_field(vm, source_file, &_object, "value", "keyword")?,
|
||||
range: range_from_object(vm, source_file, _object, "keyword")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -201,6 +216,21 @@ struct PositionalParameters {
|
||||
pub args: Box<[ast::Parameter]>,
|
||||
}
|
||||
|
||||
impl PositionalParameters {
|
||||
fn ast_from_field(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: &PyObject,
|
||||
field: &'static str,
|
||||
typ: &str,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
args: get_node_boxed_slice_field(vm, source_file, object, field, typ)?,
|
||||
_range: TextRange::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for PositionalParameters {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
BoxedSlice(self.args).ast_to_object(vm, source_file)
|
||||
@@ -214,7 +244,7 @@ impl Node for PositionalParameters {
|
||||
let args: BoxedSlice<_> = Node::ast_from_object(vm, source_file, object)?;
|
||||
Ok(Self {
|
||||
args: args.0,
|
||||
_range: TextRange::default(), // TODO
|
||||
_range: TextRange::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -224,6 +254,21 @@ struct KeywordParameters {
|
||||
pub keywords: Box<[ast::Parameter]>,
|
||||
}
|
||||
|
||||
impl KeywordParameters {
|
||||
fn ast_from_field(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: &PyObject,
|
||||
field: &'static str,
|
||||
typ: &str,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
keywords: get_node_boxed_slice_field(vm, source_file, object, field, typ)?,
|
||||
_range: TextRange::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for KeywordParameters {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
BoxedSlice(self.keywords).ast_to_object(vm, source_file)
|
||||
@@ -237,16 +282,55 @@ impl Node for KeywordParameters {
|
||||
let keywords: BoxedSlice<_> = Node::ast_from_object(vm, source_file, object)?;
|
||||
Ok(Self {
|
||||
keywords: keywords.0,
|
||||
_range: TextRange::default(), // TODO
|
||||
_range: TextRange::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct ParameterDefaults {
|
||||
pub _range: TextRange, // TODO: Use this
|
||||
runtime_defaults: Option<Vec<Option<ast::Expr>>>,
|
||||
defaults: Box<[Option<Box<ast::Expr>>]>,
|
||||
}
|
||||
|
||||
impl ParameterDefaults {
|
||||
fn ast_from_field(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: &PyObject,
|
||||
field: &'static str,
|
||||
typ: &str,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
defaults: get_node_boxed_slice_field(vm, source_file, object, field, typ)?,
|
||||
runtime_defaults: None,
|
||||
_range: TextRange::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn ast_from_field_preserve_none(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: &PyObject,
|
||||
field: &'static str,
|
||||
typ: &str,
|
||||
) -> PyResult<Self> {
|
||||
let defaults: Vec<Option<Box<ast::Expr>>> =
|
||||
get_node_list_field(vm, source_file, object, field, typ)?;
|
||||
let runtime_defaults = defaults.iter().any(Option::is_none).then(|| {
|
||||
defaults
|
||||
.iter()
|
||||
.map(|default| default.as_deref().cloned())
|
||||
.collect()
|
||||
});
|
||||
Ok(Self {
|
||||
defaults: defaults.into_boxed_slice(),
|
||||
runtime_defaults,
|
||||
_range: TextRange::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for ParameterDefaults {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
BoxedSlice(self.defaults).ast_to_object(vm, source_file)
|
||||
@@ -260,7 +344,8 @@ impl Node for ParameterDefaults {
|
||||
let defaults: BoxedSlice<_> = Node::ast_from_object(vm, source_file, object)?;
|
||||
Ok(Self {
|
||||
defaults: defaults.0,
|
||||
_range: TextRange::default(), // TODO
|
||||
runtime_defaults: None,
|
||||
_range: TextRange::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -287,6 +372,7 @@ fn extract_positional_parameter_defaults(
|
||||
.map(|item| item.range())
|
||||
.reduce(|acc, next| acc.cover(next))
|
||||
.unwrap_or_default(),
|
||||
runtime_defaults: None,
|
||||
defaults: defaults.into_boxed_slice(),
|
||||
};
|
||||
|
||||
@@ -325,14 +411,13 @@ fn merge_positional_parameter_defaults(
|
||||
vm: &VirtualMachine,
|
||||
posonlyargs: PositionalParameters,
|
||||
args: PositionalParameters,
|
||||
defaults: ParameterDefaults,
|
||||
defaults: Box<[Option<Box<ast::Expr>>]>,
|
||||
) -> PyResult<(
|
||||
Vec<ast::ParameterWithDefault>,
|
||||
Vec<ast::ParameterWithDefault>,
|
||||
)> {
|
||||
let posonlyargs = posonlyargs.args;
|
||||
let args = args.args;
|
||||
let defaults = defaults.defaults;
|
||||
|
||||
let mut posonlyargs: Vec<_> = <Box<[_]> as IntoIterator>::into_iter(posonlyargs)
|
||||
.map(|parameter| ast::ParameterWithDefault {
|
||||
@@ -383,6 +468,7 @@ fn extract_keyword_parameter_defaults(
|
||||
.map(|item| item.range())
|
||||
.reduce(|acc, next| acc.cover(next))
|
||||
.unwrap_or_default(),
|
||||
runtime_defaults: None,
|
||||
defaults: defaults.into_boxed_slice(),
|
||||
};
|
||||
|
||||
@@ -422,5 +508,5 @@ fn merge_keyword_parameter_defaults(
|
||||
default,
|
||||
range: Default::default(),
|
||||
})
|
||||
.collect())
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
@@ -10,6 +10,7 @@ impl Node for ast::MatchCase {
|
||||
guard,
|
||||
body,
|
||||
range: _,
|
||||
runtime_body,
|
||||
} = self;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodeMatchCase::static_type().to_owned())
|
||||
@@ -19,8 +20,11 @@ impl Node for ast::MatchCase {
|
||||
.unwrap();
|
||||
dict.set_item("guard", guard.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
dict.set_item("body", body.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
let body = runtime_body.map_or_else(
|
||||
|| body.ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
);
|
||||
dict.set_item("body", body, vm).unwrap();
|
||||
node.into()
|
||||
}
|
||||
|
||||
@@ -29,22 +33,18 @@ impl Node for ast::MatchCase {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let body: Vec<Option<ast::Stmt>> =
|
||||
get_node_list_field(vm, source_file, &object, "body", "match_case")?;
|
||||
let (runtime_body, body) = runtime_stmt_list_from_values(body);
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
pattern: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field_required(vm, &object, "pattern", "match_case")?,
|
||||
)?,
|
||||
pattern: get_required_node_field(vm, source_file, &object, "pattern", "match_case")?,
|
||||
guard: get_node_field_opt(vm, &object, "guard")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
body: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "body", "match_case")?,
|
||||
)?,
|
||||
body,
|
||||
range: Default::default(),
|
||||
runtime_body,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -68,64 +68,152 @@ impl Node for ast::Pattern {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let cls = object.class();
|
||||
Ok(if cls.is(pyast::NodePatternMatchValue::static_type()) {
|
||||
Self::MatchValue(ast::PatternMatchValue::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
} else if cls.is(pyast::NodePatternMatchSingleton::static_type()) {
|
||||
Self::MatchSingleton(ast::PatternMatchSingleton::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
} else if cls.is(pyast::NodePatternMatchSequence::static_type()) {
|
||||
Self::MatchSequence(ast::PatternMatchSequence::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
} else if cls.is(pyast::NodePatternMatchMapping::static_type()) {
|
||||
Self::MatchMapping(ast::PatternMatchMapping::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
} else if cls.is(pyast::NodePatternMatchClass::static_type()) {
|
||||
Self::MatchClass(ast::PatternMatchClass::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
} else if cls.is(pyast::NodePatternMatchStar::static_type()) {
|
||||
Self::MatchStar(ast::PatternMatchStar::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
} else if cls.is(pyast::NodePatternMatchAs::static_type()) {
|
||||
Self::MatchAs(ast::PatternMatchAs::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
} else if cls.is(pyast::NodePatternMatchOr::static_type()) {
|
||||
Self::MatchOr(ast::PatternMatchOr::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
if vm.is_none(&object) {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of pattern, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
}
|
||||
enum PatternKind {
|
||||
Value,
|
||||
Singleton,
|
||||
Sequence,
|
||||
Mapping,
|
||||
Class,
|
||||
Star,
|
||||
As,
|
||||
Or,
|
||||
}
|
||||
let kind = if is_node_instance(vm, &object, pyast::NodePatternMatchValue::static_type())? {
|
||||
PatternKind::Value
|
||||
} else if is_node_instance(vm, &object, pyast::NodePatternMatchSingleton::static_type())? {
|
||||
PatternKind::Singleton
|
||||
} else if is_node_instance(vm, &object, pyast::NodePatternMatchSequence::static_type())? {
|
||||
PatternKind::Sequence
|
||||
} else if is_node_instance(vm, &object, pyast::NodePatternMatchMapping::static_type())? {
|
||||
PatternKind::Mapping
|
||||
} else if is_node_instance(vm, &object, pyast::NodePatternMatchClass::static_type())? {
|
||||
PatternKind::Class
|
||||
} else if is_node_instance(vm, &object, pyast::NodePatternMatchStar::static_type())? {
|
||||
PatternKind::Star
|
||||
} else if is_node_instance(vm, &object, pyast::NodePatternMatchAs::static_type())? {
|
||||
PatternKind::As
|
||||
} else if is_node_instance(vm, &object, pyast::NodePatternMatchOr::static_type())? {
|
||||
PatternKind::Or
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of pattern, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
};
|
||||
let range = pattern_range_from_object(vm, source_file, object.clone())?;
|
||||
Ok(match kind {
|
||||
PatternKind::Value => Self::MatchValue(pattern_match_value_from_object_with_range(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
range,
|
||||
)?),
|
||||
PatternKind::Singleton => Self::MatchSingleton(
|
||||
pattern_match_singleton_from_object_with_range(vm, source_file, object, range)?,
|
||||
),
|
||||
PatternKind::Sequence => Self::MatchSequence(
|
||||
pattern_match_sequence_from_object_with_range(vm, source_file, object, range)?,
|
||||
),
|
||||
PatternKind::Mapping => Self::MatchMapping(
|
||||
pattern_match_mapping_from_object_with_range(vm, source_file, object, range)?,
|
||||
),
|
||||
PatternKind::Class => Self::MatchClass(pattern_match_class_from_object_with_range(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
range,
|
||||
)?),
|
||||
PatternKind::Star => Self::MatchStar(pattern_match_star_from_object_with_range(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
range,
|
||||
)?),
|
||||
PatternKind::As => Self::MatchAs(pattern_match_as_from_object_with_range(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
range,
|
||||
)?),
|
||||
PatternKind::Or => Self::MatchOr(pattern_match_or_from_object_with_range(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
range,
|
||||
)?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn null_pattern_placeholder(range: TextRange) -> ast::Pattern {
|
||||
ast::Pattern::MatchAs(ast::PatternMatchAs {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
pattern: None,
|
||||
name: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn lower_nullable_patterns(values: &[Option<ast::Pattern>], range: TextRange) -> Vec<ast::Pattern> {
|
||||
values
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|value| value.unwrap_or_else(|| null_pattern_placeholder(range)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn null_expr_placeholder(range: TextRange) -> ast::Expr {
|
||||
ast::Expr::NoneLiteral(ast::ExprNoneLiteral {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
})
|
||||
}
|
||||
|
||||
fn lower_nullable_exprs(values: &[Option<ast::Expr>], range: TextRange) -> Vec<ast::Expr> {
|
||||
values
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|value| value.unwrap_or_else(|| null_expr_placeholder(range)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
type RuntimePatternList = Option<Vec<Option<ast::Pattern>>>;
|
||||
type PatternListField = (RuntimePatternList, Vec<ast::Pattern>);
|
||||
|
||||
fn pattern_list_from_field(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: &PyObject,
|
||||
field: &'static str,
|
||||
typ: &str,
|
||||
range: TextRange,
|
||||
) -> PyResult<PatternListField> {
|
||||
let values: Vec<Option<ast::Pattern>> =
|
||||
get_node_list_field(vm, source_file, object, field, typ)?;
|
||||
let runtime_patterns = values.iter().any(Option::is_none).then(|| values.clone());
|
||||
Ok((runtime_patterns, lower_nullable_patterns(&values, range)))
|
||||
}
|
||||
|
||||
// constructor
|
||||
fn pattern_match_value_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<ast::PatternMatchValue> {
|
||||
Ok(ast::PatternMatchValue {
|
||||
node_index: Default::default(),
|
||||
value: get_required_node_field(vm, source_file, &object, "value", "MatchValue")?,
|
||||
range,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for ast::PatternMatchValue {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
@@ -148,19 +236,29 @@ impl Node for ast::PatternMatchValue {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
value: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "value", "MatchValue")?,
|
||||
)?,
|
||||
range: range_from_object(vm, source_file, object, "MatchValue")?,
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "MatchValue")?;
|
||||
pattern_match_value_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
fn pattern_match_singleton_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<ast::PatternMatchSingleton> {
|
||||
Ok(ast::PatternMatchSingleton {
|
||||
node_index: Default::default(),
|
||||
value: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "value", "MatchSingleton")?,
|
||||
)?,
|
||||
range,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for ast::PatternMatchSingleton {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
@@ -186,15 +284,8 @@ impl Node for ast::PatternMatchSingleton {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
value: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "value", "MatchSingleton")?,
|
||||
)?,
|
||||
range: range_from_object(vm, source_file, object, "MatchSingleton")?,
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "MatchSingleton")?;
|
||||
pattern_match_singleton_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,21 +310,35 @@ impl Node for ast::Singleton {
|
||||
} else if object.is(&vm.ctx.false_value) {
|
||||
Ok(Self::False)
|
||||
} else {
|
||||
Err(vm.new_value_error(format!(
|
||||
"Expected None, True, or False, got {:?}",
|
||||
object.class().name()
|
||||
)))
|
||||
Err(vm.new_value_error("MatchSingleton can only contain True, False and None"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
fn pattern_match_sequence_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<ast::PatternMatchSequence> {
|
||||
let (runtime_patterns, patterns) =
|
||||
pattern_list_from_field(vm, source_file, &object, "patterns", "MatchSequence", range)?;
|
||||
Ok(ast::PatternMatchSequence {
|
||||
node_index: Default::default(),
|
||||
patterns: patterns.to_vec(),
|
||||
range,
|
||||
runtime_patterns,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for ast::PatternMatchSequence {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
node_index: _,
|
||||
patterns,
|
||||
range,
|
||||
runtime_patterns,
|
||||
} = self;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(
|
||||
@@ -242,8 +347,11 @@ impl Node for ast::PatternMatchSequence {
|
||||
)
|
||||
.unwrap();
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item("patterns", patterns.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
let patterns = runtime_patterns.map_or_else(
|
||||
|| patterns.ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
);
|
||||
dict.set_item("patterns", patterns, vm).unwrap();
|
||||
node_add_location(&dict, range, vm, source_file);
|
||||
node.into()
|
||||
}
|
||||
@@ -253,19 +361,40 @@ impl Node for ast::PatternMatchSequence {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
patterns: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "patterns", "MatchSequence")?,
|
||||
)?,
|
||||
range: range_from_object(vm, source_file, object, "MatchSequence")?,
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "MatchSequence")?;
|
||||
pattern_match_sequence_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
fn pattern_match_mapping_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<ast::PatternMatchMapping> {
|
||||
let keys: Vec<Option<ast::Expr>> =
|
||||
get_node_list_field(vm, source_file, &object, "keys", "MatchMapping")?;
|
||||
let patterns: Vec<Option<ast::Pattern>> =
|
||||
get_node_list_field(vm, source_file, &object, "patterns", "MatchMapping")?;
|
||||
let runtime_keys = keys.iter().any(Option::is_none).then(|| keys.clone());
|
||||
let runtime_patterns = patterns
|
||||
.iter()
|
||||
.any(Option::is_none)
|
||||
.then(|| patterns.clone());
|
||||
Ok(ast::PatternMatchMapping {
|
||||
node_index: Default::default(),
|
||||
keys: lower_nullable_exprs(&keys, range),
|
||||
patterns: lower_nullable_patterns(&patterns, range),
|
||||
rest: get_node_field_opt(vm, &object, "rest")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
range,
|
||||
runtime_keys,
|
||||
runtime_patterns,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for ast::PatternMatchMapping {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
@@ -274,15 +403,23 @@ impl Node for ast::PatternMatchMapping {
|
||||
patterns,
|
||||
rest,
|
||||
range,
|
||||
runtime_keys,
|
||||
runtime_patterns,
|
||||
} = self;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodePatternMatchMapping::static_type().to_owned())
|
||||
.unwrap();
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item("keys", keys.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
dict.set_item("patterns", patterns.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
let keys = runtime_keys.map_or_else(
|
||||
|| keys.ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
);
|
||||
dict.set_item("keys", keys, vm).unwrap();
|
||||
let patterns = runtime_patterns.map_or_else(
|
||||
|| patterns.ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
);
|
||||
dict.set_item("patterns", patterns, vm).unwrap();
|
||||
dict.set_item("rest", rest.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
node_add_location(&dict, range, vm, source_file);
|
||||
@@ -294,27 +431,57 @@ impl Node for ast::PatternMatchMapping {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
keys: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "keys", "MatchMapping")?,
|
||||
)?,
|
||||
patterns: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "patterns", "MatchMapping")?,
|
||||
)?,
|
||||
rest: get_node_field_opt(vm, &object, "rest")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
range: range_from_object(vm, source_file, object, "MatchMapping")?,
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "MatchMapping")?;
|
||||
pattern_match_mapping_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
fn pattern_match_class_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<ast::PatternMatchClass> {
|
||||
let cls = get_required_node_field(vm, source_file, &object, "cls", "MatchClass")?;
|
||||
let patterns: Vec<Option<ast::Pattern>> =
|
||||
get_node_list_field(vm, source_file, &object, "patterns", "MatchClass")?;
|
||||
let kwd_attrs = PatternMatchClassKeywordAttributes::ast_from_field(
|
||||
vm,
|
||||
source_file,
|
||||
&object,
|
||||
"kwd_attrs",
|
||||
"MatchClass",
|
||||
)?;
|
||||
let kwd_patterns: Vec<Option<ast::Pattern>> =
|
||||
get_node_list_field(vm, source_file, &object, "kwd_patterns", "MatchClass")?;
|
||||
let has_runtime_shape = kwd_attrs.0.len() != kwd_patterns.len()
|
||||
|| patterns.iter().any(Option::is_none)
|
||||
|| kwd_patterns.iter().any(Option::is_none);
|
||||
let runtime_patterns = has_runtime_shape.then(|| patterns.clone());
|
||||
let runtime_kwd_attrs = has_runtime_shape.then(|| kwd_attrs.0.clone());
|
||||
let runtime_kwd_patterns = has_runtime_shape.then(|| kwd_patterns.clone());
|
||||
let patterns = PatternMatchClassPatterns(lower_nullable_patterns(&patterns, range));
|
||||
let kwd_patterns =
|
||||
PatternMatchClassKeywordPatterns(lower_nullable_patterns(&kwd_patterns, range));
|
||||
let (patterns, keywords) = merge_pattern_match_class(patterns, kwd_attrs, kwd_patterns);
|
||||
|
||||
Ok(ast::PatternMatchClass {
|
||||
node_index: Default::default(),
|
||||
cls,
|
||||
range,
|
||||
arguments: ast::PatternArguments {
|
||||
node_index: Default::default(),
|
||||
range: Default::default(),
|
||||
patterns,
|
||||
keywords,
|
||||
},
|
||||
runtime_patterns,
|
||||
runtime_kwd_attrs,
|
||||
runtime_kwd_patterns,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for ast::PatternMatchClass {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
@@ -322,24 +489,36 @@ impl Node for ast::PatternMatchClass {
|
||||
cls,
|
||||
arguments,
|
||||
range,
|
||||
runtime_patterns,
|
||||
runtime_kwd_attrs,
|
||||
runtime_kwd_patterns,
|
||||
} = self;
|
||||
let (patterns, kwd_attrs, kwd_patterns) = split_pattern_match_class(arguments);
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodePatternMatchClass::static_type().to_owned())
|
||||
.unwrap();
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item("cls", cls.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
dict.set_item("patterns", patterns.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
dict.set_item("kwd_attrs", kwd_attrs.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
dict.set_item(
|
||||
"kwd_patterns",
|
||||
kwd_patterns.ast_to_object(vm, source_file),
|
||||
vm,
|
||||
)
|
||||
.unwrap();
|
||||
let (patterns, kwd_attrs, kwd_patterns) =
|
||||
if let (Some(patterns), Some(kwd_attrs), Some(kwd_patterns)) =
|
||||
(runtime_patterns, runtime_kwd_attrs, runtime_kwd_patterns)
|
||||
{
|
||||
(
|
||||
patterns.ast_to_object(vm, source_file),
|
||||
kwd_attrs.ast_to_object(vm, source_file),
|
||||
kwd_patterns.ast_to_object(vm, source_file),
|
||||
)
|
||||
} else {
|
||||
let (patterns, kwd_attrs, kwd_patterns) = split_pattern_match_class(arguments);
|
||||
(
|
||||
patterns.ast_to_object(vm, source_file),
|
||||
kwd_attrs.ast_to_object(vm, source_file),
|
||||
kwd_patterns.ast_to_object(vm, source_file),
|
||||
)
|
||||
};
|
||||
dict.set_item("patterns", patterns, vm).unwrap();
|
||||
dict.set_item("kwd_attrs", kwd_attrs, vm).unwrap();
|
||||
dict.set_item("kwd_patterns", kwd_patterns, vm).unwrap();
|
||||
node_add_location(&dict, range, vm, source_file);
|
||||
node.into()
|
||||
}
|
||||
@@ -349,41 +528,8 @@ impl Node for ast::PatternMatchClass {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let patterns = Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "patterns", "MatchClass")?,
|
||||
)?;
|
||||
let kwd_attrs: PatternMatchClassKeywordAttributes = Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "kwd_attrs", "MatchClass")?,
|
||||
)?;
|
||||
let kwd_patterns: PatternMatchClassKeywordPatterns = Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "kwd_patterns", "MatchClass")?,
|
||||
)?;
|
||||
if kwd_attrs.0.len() != kwd_patterns.0.len() {
|
||||
return Err(vm.new_value_error("MatchClass has mismatched kwd_attrs and kwd_patterns"));
|
||||
}
|
||||
let (patterns, keywords) = merge_pattern_match_class(patterns, kwd_attrs, kwd_patterns);
|
||||
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
cls: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "cls", "MatchClass")?,
|
||||
)?,
|
||||
range: range_from_object(vm, source_file, object, "MatchClass")?,
|
||||
arguments: ast::PatternArguments {
|
||||
node_index: Default::default(),
|
||||
range: Default::default(),
|
||||
patterns,
|
||||
keywords,
|
||||
},
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "MatchClass")?;
|
||||
pattern_match_class_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,6 +551,24 @@ impl Node for PatternMatchClassPatterns {
|
||||
|
||||
struct PatternMatchClassKeywordAttributes(Vec<ast::Identifier>);
|
||||
|
||||
impl PatternMatchClassKeywordAttributes {
|
||||
fn ast_from_field(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: &PyObject,
|
||||
field: &'static str,
|
||||
typ: &str,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self(get_node_list_field(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
field,
|
||||
typ,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for PatternMatchClassKeywordAttributes {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
self.0.ast_to_object(vm, source_file)
|
||||
@@ -435,6 +599,21 @@ impl Node for PatternMatchClassKeywordPatterns {
|
||||
}
|
||||
}
|
||||
// constructor
|
||||
fn pattern_match_star_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<ast::PatternMatchStar> {
|
||||
Ok(ast::PatternMatchStar {
|
||||
node_index: Default::default(),
|
||||
name: get_node_field_opt(vm, &object, "name")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
range,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for ast::PatternMatchStar {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
@@ -457,17 +636,30 @@ impl Node for ast::PatternMatchStar {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
name: get_node_field_opt(vm, &object, "name")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
range: range_from_object(vm, source_file, object, "MatchStar")?,
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "MatchStar")?;
|
||||
pattern_match_star_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
fn pattern_match_as_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<ast::PatternMatchAs> {
|
||||
Ok(ast::PatternMatchAs {
|
||||
node_index: Default::default(),
|
||||
pattern: get_node_field_opt(vm, &object, "pattern")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
name: get_node_field_opt(vm, &object, "name")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
range,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for ast::PatternMatchAs {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
@@ -493,33 +685,45 @@ impl Node for ast::PatternMatchAs {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
pattern: get_node_field_opt(vm, &object, "pattern")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
name: get_node_field_opt(vm, &object, "name")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
range: range_from_object(vm, source_file, object, "MatchAs")?,
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "MatchAs")?;
|
||||
pattern_match_as_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
fn pattern_match_or_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<ast::PatternMatchOr> {
|
||||
let (runtime_patterns, patterns) =
|
||||
pattern_list_from_field(vm, source_file, &object, "patterns", "MatchOr", range)?;
|
||||
Ok(ast::PatternMatchOr {
|
||||
node_index: Default::default(),
|
||||
patterns: patterns.to_vec(),
|
||||
range,
|
||||
runtime_patterns,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for ast::PatternMatchOr {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
node_index: _,
|
||||
patterns,
|
||||
range,
|
||||
runtime_patterns,
|
||||
} = self;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodePatternMatchOr::static_type().to_owned())
|
||||
.unwrap();
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item("patterns", patterns.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
let patterns = runtime_patterns.map_or_else(
|
||||
|| patterns.ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
);
|
||||
dict.set_item("patterns", patterns, vm).unwrap();
|
||||
node_add_location(&dict, range, vm, source_file);
|
||||
node.into()
|
||||
}
|
||||
@@ -528,15 +732,8 @@ impl Node for ast::PatternMatchOr {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
patterns: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "patterns", "MatchOr")?,
|
||||
)?,
|
||||
range: range_from_object(vm, source_file, object, "MatchOr")?,
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "MatchOr")?;
|
||||
pattern_match_or_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::builtins::{PyGenericAlias, PyTuple, PyTupleRef, PyTypeRef, make_union
|
||||
use crate::common::ascii;
|
||||
use crate::convert::ToPyObject;
|
||||
use crate::function::FuncArgs;
|
||||
use crate::types::Initializer;
|
||||
|
||||
macro_rules! impl_node {
|
||||
(
|
||||
@@ -61,6 +60,12 @@ macro_rules! impl_node {
|
||||
macro_rules! impl_base_node {
|
||||
// Base node without fields/attributes (e.g. NodeMod, NodeExpr)
|
||||
($name:ident) => {
|
||||
impl_base_node!($name, attributes: []);
|
||||
};
|
||||
($name:ident, attributes: [$($attr:expr),* $(,)?]) => {
|
||||
impl_base_node!($name, attributes: [$($attr),*], optional_end_location: false);
|
||||
};
|
||||
($name:ident, attributes: [$($attr:expr),* $(,)?], optional_end_location: $optional_end_location:expr) => {
|
||||
#[pyclass(flags(HAS_DICT, BASETYPE))]
|
||||
impl $name {
|
||||
#[pymethod]
|
||||
@@ -83,9 +88,24 @@ macro_rules! impl_base_node {
|
||||
(*flags).remove(crate::types::PyTypeFlags::IMMUTABLETYPE);
|
||||
}
|
||||
class.set_attr(
|
||||
identifier!(ctx, _attributes),
|
||||
identifier!(ctx, _fields),
|
||||
ctx.empty_tuple.clone().into(),
|
||||
);
|
||||
class.set_str_attr("__match_args__", ctx.empty_tuple.clone(), ctx);
|
||||
class.set_attr(
|
||||
identifier!(ctx, _attributes),
|
||||
ctx.new_tuple(vec![
|
||||
$(
|
||||
ctx.new_str(ascii!($attr)).into()
|
||||
),*
|
||||
])
|
||||
.into(),
|
||||
);
|
||||
if $optional_end_location {
|
||||
let none = ctx.none();
|
||||
class.set_str_attr("end_lineno", none.clone(), ctx);
|
||||
class.set_str_attr("end_col_offset", none, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -179,7 +199,11 @@ impl_node!(
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct NodeStmt(NodeAst);
|
||||
|
||||
impl_base_node!(NodeStmt);
|
||||
impl_base_node!(
|
||||
NodeStmt,
|
||||
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
|
||||
optional_end_location: true
|
||||
);
|
||||
|
||||
impl_node!(
|
||||
#[pyclass(module = "_ast", name = "FunctionType", base = NodeMod)]
|
||||
@@ -378,7 +402,11 @@ impl_node!(
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct NodeExpr(NodeAst);
|
||||
|
||||
impl_base_node!(NodeExpr);
|
||||
impl_base_node!(
|
||||
NodeExpr,
|
||||
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
|
||||
optional_end_location: true
|
||||
);
|
||||
|
||||
impl_node!(
|
||||
#[pyclass(module = "_ast", name = "Continue", base = NodeStmt)]
|
||||
@@ -533,12 +561,11 @@ impl_node!(
|
||||
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
|
||||
);
|
||||
|
||||
// NodeExprConstant needs custom Initializer to default kind to None
|
||||
#[pyclass(module = "_ast", name = "Constant", base = NodeExpr)]
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct NodeExprConstant(NodeExpr);
|
||||
|
||||
#[pyclass(flags(HAS_DICT, BASETYPE), with(Initializer))]
|
||||
#[pyclass(flags(HAS_DICT, BASETYPE))]
|
||||
impl NodeExprConstant {
|
||||
#[extend_class]
|
||||
fn extend_class_with_fields(ctx: &Context, class: &'static Py<PyType>) {
|
||||
@@ -580,24 +607,6 @@ impl NodeExprConstant {
|
||||
}
|
||||
}
|
||||
|
||||
impl Initializer for NodeExprConstant {
|
||||
type Args = FuncArgs;
|
||||
|
||||
fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
|
||||
<NodeAst as Initializer>::slot_init(zelf.clone(), args, vm)?;
|
||||
// kind defaults to None if not provided
|
||||
let dict = zelf.as_object().dict().unwrap();
|
||||
if !dict.contains_key("kind", vm) {
|
||||
dict.set_item("kind", vm.ctx.none(), vm)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
|
||||
unreachable!("slot_init is defined")
|
||||
}
|
||||
}
|
||||
|
||||
impl_node!(
|
||||
#[pyclass(module = "_ast", name = "Attribute", base = NodeExpr)]
|
||||
pub(crate) struct NodeExprAttribute,
|
||||
@@ -841,7 +850,11 @@ impl_node!(
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct NodeExceptHandler(NodeAst);
|
||||
|
||||
impl_base_node!(NodeExceptHandler);
|
||||
impl_base_node!(
|
||||
NodeExceptHandler,
|
||||
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
|
||||
optional_end_location: true
|
||||
);
|
||||
|
||||
impl_node!(
|
||||
#[pyclass(module = "_ast", name = "comprehension", base = NodeAst)]
|
||||
@@ -893,7 +906,10 @@ impl_node!(
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct NodePattern(NodeAst);
|
||||
|
||||
impl_base_node!(NodePattern);
|
||||
impl_base_node!(
|
||||
NodePattern,
|
||||
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"]
|
||||
);
|
||||
|
||||
impl_node!(
|
||||
#[pyclass(module = "_ast", name = "match_case", base = NodeAst)]
|
||||
@@ -967,7 +983,10 @@ impl_node!(
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct NodeTypeParam(NodeAst);
|
||||
|
||||
impl_base_node!(NodeTypeParam);
|
||||
impl_base_node!(
|
||||
NodeTypeParam,
|
||||
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"]
|
||||
);
|
||||
|
||||
impl_node!(
|
||||
#[pyclass(module = "_ast", name = "TypeIgnore", base = NodeTypeIgnore)]
|
||||
@@ -1681,7 +1700,6 @@ fn populate_field_types(vm: &VirtualMachine, module: &Py<PyModule>) {
|
||||
|
||||
let field_types_attr = vm.ctx.intern_str("_field_types");
|
||||
let annotations_attr = vm.ctx.intern_str("__annotations__");
|
||||
let empty_dict: PyObjectRef = vm.ctx.new_dict().into();
|
||||
|
||||
for &(class_name, fields) in FIELD_TYPES {
|
||||
if fields.is_empty() {
|
||||
@@ -1752,36 +1770,6 @@ fn populate_field_types(vm: &VirtualMachine, module: &Py<PyModule>) {
|
||||
type_obj.set_attr(annotations_attr, field_types);
|
||||
}
|
||||
}
|
||||
|
||||
// Base AST classes (e.g., expr, stmt) should still expose __annotations__.
|
||||
const BASE_AST_TYPES: &[&str] = &[
|
||||
"mod",
|
||||
"stmt",
|
||||
"expr",
|
||||
"expr_context",
|
||||
"boolop",
|
||||
"operator",
|
||||
"unaryop",
|
||||
"cmpop",
|
||||
"excepthandler",
|
||||
"pattern",
|
||||
"type_ignore",
|
||||
"type_param",
|
||||
];
|
||||
for &class_name in BASE_AST_TYPES {
|
||||
let class = module
|
||||
.get_attr(class_name, vm)
|
||||
.unwrap_or_else(|_| panic!("AST class '{class_name}' not found in module"));
|
||||
let Some(type_obj) = class.downcast_ref::<PyType>() else {
|
||||
continue;
|
||||
};
|
||||
if type_obj.get_attr(field_types_attr).is_none() {
|
||||
type_obj.set_attr(field_types_attr, empty_dict.clone());
|
||||
}
|
||||
if type_obj.get_attr(annotations_attr).is_none() {
|
||||
type_obj.set_attr(annotations_attr, empty_dict.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn populate_singletons(vm: &VirtualMachine, module: &Py<PyModule>) {
|
||||
|
||||
@@ -7,13 +7,10 @@ use super::{
|
||||
#[pymodule]
|
||||
pub(crate) mod _ast {
|
||||
use crate::{
|
||||
AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
builtins::{
|
||||
PyDictRef, PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef, PyUtf8Str, PyUtf8StrRef,
|
||||
},
|
||||
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
builtins::{PyDictRef, PySet, PyStr, PyTupleRef, PyType, PyTypeRef, PyUtf8Str},
|
||||
class::{PyClassImpl, StaticType},
|
||||
common::wtf8::Wtf8,
|
||||
function::{FuncArgs, KwArgs, PyMethodDef, PyMethodFlags},
|
||||
function::{ArgIterable, FuncArgs, KwArgs, PyMethodDef, PyMethodFlags},
|
||||
stdlib::_ast::repr,
|
||||
types::{Constructor, Initializer},
|
||||
warn,
|
||||
@@ -117,10 +114,12 @@ pub(crate) mod _ast {
|
||||
|
||||
let fields = cls.get_attr(vm.ctx.intern_str("_fields"));
|
||||
if let Some(fields) = fields {
|
||||
let fields: Vec<PyStrRef> = fields.try_to_value(vm)?;
|
||||
let fields = fields.sequence_unchecked();
|
||||
let numfields = fields.length(vm)?;
|
||||
let mut positional: Vec<PyObjectRef> = Vec::new();
|
||||
for field in fields {
|
||||
if dict.get_item_opt::<Wtf8>(field.as_wtf8(), vm)?.is_some() {
|
||||
for i in 0..numfields {
|
||||
let field = fields.get_item(i as isize, vm)?;
|
||||
if dict.get_item_opt(&*field, vm)?.is_some() {
|
||||
positional.push(vm.ctx.none());
|
||||
} else {
|
||||
break;
|
||||
@@ -136,6 +135,85 @@ pub(crate) mod _ast {
|
||||
.new_tuple(vec![type_obj, vm.ctx.new_tuple(vec![]).into(), dict.into()]))
|
||||
}
|
||||
|
||||
fn ast_replace_update_payload(
|
||||
payload: &PyDictRef,
|
||||
keys: Option<&PyObjectRef>,
|
||||
dict: &PyDictRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let Some(keys) = keys else {
|
||||
return Ok(());
|
||||
};
|
||||
let keys = keys.sequence_unchecked();
|
||||
let num_keys = keys.length(vm)?;
|
||||
for i in 0..num_keys {
|
||||
let key = keys.get_item(i as isize, vm)?;
|
||||
if let Some(value) = dict.get_item_opt(&*key, vm)? {
|
||||
payload.set_item(&*key, value, vm)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ast_replace_set_update(
|
||||
expecting: &PyRef<PySet>,
|
||||
iterable: Option<&PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let Some(iterable) = iterable else {
|
||||
return Ok(());
|
||||
};
|
||||
let iterable = iterable.clone().try_into_value::<ArgIterable>(vm)?;
|
||||
for item in iterable.iter(vm)? {
|
||||
expecting.add(item?, vm)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ast_replace_set_discard(
|
||||
expecting: &PyRef<PySet>,
|
||||
key: &PyObject,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<bool> {
|
||||
let contained = expecting
|
||||
.as_object()
|
||||
.sequence_unchecked()
|
||||
.contains(key, vm)?;
|
||||
if contained {
|
||||
vm.call_method(expecting.as_object(), "discard", (key.to_owned(),))?;
|
||||
}
|
||||
Ok(contained)
|
||||
}
|
||||
|
||||
fn ast_replace_set_difference_update(
|
||||
expecting: &PyRef<PySet>,
|
||||
iterable: Option<&PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let Some(iterable) = iterable else {
|
||||
return Ok(());
|
||||
};
|
||||
let iterable = iterable.clone().try_into_value::<ArgIterable>(vm)?;
|
||||
for item in iterable.iter(vm)? {
|
||||
let item = item?;
|
||||
ast_replace_set_discard(expecting, &item, vm)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ast_set_attr(
|
||||
obj: &PyObject,
|
||||
name: &PyObject,
|
||||
value: impl Into<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let name = name
|
||||
.to_owned()
|
||||
.downcast::<PyStr>()
|
||||
.map_err(|_| vm.new_type_error("attribute name must be string"))?;
|
||||
obj.set_attr(&name, value, vm)
|
||||
}
|
||||
|
||||
pub(crate) fn ast_replace(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
if !args.args.is_empty() {
|
||||
return Err(vm.new_type_error("__replace__() takes no positional arguments"));
|
||||
@@ -146,22 +224,13 @@ pub(crate) mod _ast {
|
||||
let attributes = cls.get_attr(vm.ctx.intern_str("_attributes"));
|
||||
let dict = zelf.as_object().dict();
|
||||
|
||||
let mut expecting: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
if let Some(fields) = fields.clone() {
|
||||
let fields: Vec<PyUtf8StrRef> = fields.try_to_value(vm)?;
|
||||
for field in fields {
|
||||
expecting.insert(field.as_str().to_owned());
|
||||
}
|
||||
}
|
||||
if let Some(attributes) = attributes.clone() {
|
||||
let attributes: Vec<PyUtf8StrRef> = attributes.try_to_value(vm)?;
|
||||
for attr in attributes {
|
||||
expecting.insert(attr.as_str().to_owned());
|
||||
}
|
||||
}
|
||||
let expecting = PySet::default().into_ref(&vm.ctx);
|
||||
ast_replace_set_update(&expecting, fields.as_ref(), vm)?;
|
||||
ast_replace_set_update(&expecting, attributes.as_ref(), vm)?;
|
||||
|
||||
for (key, _value) in &args.kwargs {
|
||||
if !expecting.remove(key) {
|
||||
let key_obj: PyObjectRef = vm.ctx.new_str(key.as_str()).into();
|
||||
if !ast_replace_set_discard(&expecting, &key_obj, vm)? {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{}.__replace__ got an unexpected keyword argument '{}'.",
|
||||
cls.name(),
|
||||
@@ -172,16 +241,9 @@ pub(crate) mod _ast {
|
||||
|
||||
if let Some(dict) = dict.as_ref() {
|
||||
for (key, _value) in dict.items_vec() {
|
||||
if let Ok(key) = key.downcast::<PyUtf8Str>() {
|
||||
expecting.remove(key.as_str());
|
||||
}
|
||||
}
|
||||
if let Some(attributes) = attributes.clone() {
|
||||
let attributes: Vec<PyUtf8StrRef> = attributes.try_to_value(vm)?;
|
||||
for attr in attributes {
|
||||
expecting.remove(attr.as_str());
|
||||
}
|
||||
ast_replace_set_discard(&expecting, &key, vm)?;
|
||||
}
|
||||
ast_replace_set_difference_update(&expecting, attributes.as_ref(), vm)?;
|
||||
}
|
||||
|
||||
// Discard optional fields (T | None).
|
||||
@@ -189,20 +251,18 @@ pub(crate) mod _ast {
|
||||
&& let Ok(field_types) = field_types.downcast::<crate::builtins::PyDict>()
|
||||
{
|
||||
for (key, value) in field_types.items_vec() {
|
||||
let Ok(key) = key.downcast::<PyUtf8Str>() else {
|
||||
continue;
|
||||
};
|
||||
if value.fast_isinstance(vm.ctx.types.union_type) {
|
||||
expecting.remove(key.as_str());
|
||||
ast_replace_set_discard(&expecting, &key, vm)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !expecting.is_empty() {
|
||||
let mut names: Vec<String> = expecting
|
||||
.into_iter()
|
||||
.map(|name| format!("{name:?}"))
|
||||
.collect();
|
||||
let remaining = expecting.elements();
|
||||
if !remaining.is_empty() {
|
||||
let mut names = Vec::with_capacity(remaining.len());
|
||||
for name in &remaining {
|
||||
names.push(name.repr(vm)?.to_string());
|
||||
}
|
||||
names.sort();
|
||||
let missing = names.join(", ");
|
||||
let count = names.len();
|
||||
@@ -217,22 +277,8 @@ pub(crate) mod _ast {
|
||||
|
||||
let payload = vm.ctx.new_dict();
|
||||
if let Some(dict) = dict {
|
||||
if let Some(fields) = fields {
|
||||
let fields: Vec<PyStrRef> = fields.try_to_value(vm)?;
|
||||
for field in fields {
|
||||
if let Some(value) = dict.get_item_opt::<Wtf8>(field.as_wtf8(), vm)? {
|
||||
payload.set_item(field.as_object(), value, vm)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(attributes) = attributes {
|
||||
let attributes: Vec<PyStrRef> = attributes.try_to_value(vm)?;
|
||||
for attr in attributes {
|
||||
if let Some(value) = dict.get_item_opt::<Wtf8>(attr.as_wtf8(), vm)? {
|
||||
payload.set_item(attr.as_object(), value, vm)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
ast_replace_update_payload(&payload, fields.as_ref(), &dict, vm)?;
|
||||
ast_replace_update_payload(&payload, attributes.as_ref(), &dict, vm)?;
|
||||
}
|
||||
for (key, value) in args.kwargs {
|
||||
payload.set_item(vm.ctx.intern_str(key), value, vm)?;
|
||||
@@ -327,7 +373,7 @@ pub(crate) mod _ast {
|
||||
}
|
||||
|
||||
fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
|
||||
unimplemented!("use slot_new")
|
||||
unreachable!("NodeAst construction is handled by slot_new")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,52 +396,55 @@ pub(crate) mod _ast {
|
||||
zelf.class().name()
|
||||
))
|
||||
})?;
|
||||
let fields: Vec<PyUtf8StrRef> = fields.try_to_value(vm)?;
|
||||
let fields_seq = fields.sequence_unchecked();
|
||||
let numfields = fields_seq.length(vm)?;
|
||||
let remaining_fields = PySet::default().into_ref(&vm.ctx);
|
||||
ast_replace_set_update(&remaining_fields, Some(&fields), vm)?;
|
||||
let n_args = args.args.len();
|
||||
if n_args > fields.len() {
|
||||
if n_args > numfields {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{} constructor takes at most {} positional argument{}",
|
||||
zelf.class().name(),
|
||||
fields.len(),
|
||||
if fields.len() == 1 { "" } else { "s" },
|
||||
numfields,
|
||||
if numfields == 1 { "" } else { "s" },
|
||||
)));
|
||||
}
|
||||
|
||||
// Track which fields were set
|
||||
let mut set_fields = std::collections::HashSet::new();
|
||||
let mut attributes: Option<Vec<PyStrRef>> = None;
|
||||
let mut attributes: Option<PyObjectRef> = None;
|
||||
|
||||
for (name, arg) in fields.iter().zip(args.args) {
|
||||
zelf.set_attr(name, arg, vm)?;
|
||||
set_fields.insert(name.as_str().to_owned());
|
||||
for (i, arg) in args.args.into_iter().enumerate() {
|
||||
let name = fields_seq.get_item(i as isize, vm)?;
|
||||
ast_set_attr(&zelf, &name, arg, vm)?;
|
||||
ast_replace_set_discard(&remaining_fields, &name, vm)?;
|
||||
}
|
||||
for (key, value) in args.kwargs {
|
||||
if let Some(pos) = fields.iter().position(|f| f.as_bytes() == key.as_bytes())
|
||||
&& pos < n_args
|
||||
{
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{} got multiple values for argument '{}'",
|
||||
zelf.class().name(),
|
||||
key
|
||||
)));
|
||||
}
|
||||
|
||||
if fields
|
||||
.iter()
|
||||
.all(|field| field.as_bytes() != key.as_bytes())
|
||||
{
|
||||
let attrs = if let Some(attrs) = &attributes {
|
||||
attrs
|
||||
let key_obj: PyObjectRef = vm.ctx.new_str(key.as_str()).into();
|
||||
let contains = fields_seq.contains(&key_obj, vm)?;
|
||||
if contains {
|
||||
if !ast_replace_set_discard(&remaining_fields, &key_obj, vm)? {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{} got multiple values for argument '{}'",
|
||||
zelf.class().name(),
|
||||
key
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
let attrs = if let Some(attributes) = &attributes {
|
||||
attributes
|
||||
} else {
|
||||
let attrs = zelf
|
||||
.class()
|
||||
.get_attr(vm.ctx.intern_str("_attributes"))
|
||||
.and_then(|attr| attr.try_to_value::<Vec<PyStrRef>>(vm).ok())
|
||||
.unwrap_or_default();
|
||||
.ok_or_else(|| {
|
||||
vm.new_attribute_error(format!(
|
||||
"type object '{}' has no attribute '_attributes'",
|
||||
zelf.class().name()
|
||||
))
|
||||
})?;
|
||||
attributes = Some(attrs);
|
||||
attributes.as_ref().unwrap()
|
||||
};
|
||||
if attrs.iter().all(|attr| attr.as_bytes() != key.as_bytes()) {
|
||||
if !attrs.sequence_unchecked().contains(&key_obj, vm)? {
|
||||
let message = vm.ctx.new_str(format!(
|
||||
"{}.__init__ got an unexpected keyword argument '{}'. \
|
||||
Support for arbitrary keyword arguments is deprecated and will be removed in Python 3.15.",
|
||||
@@ -412,7 +461,6 @@ Support for arbitrary keyword arguments is deprecated and will be removed in Pyt
|
||||
}
|
||||
}
|
||||
|
||||
set_fields.insert(key.clone());
|
||||
zelf.set_attr(vm.ctx.intern_str(key), value, vm)?;
|
||||
}
|
||||
|
||||
@@ -425,17 +473,14 @@ Support for arbitrary keyword arguments is deprecated and will be removed in Pyt
|
||||
let expr_ctx_type: PyObjectRef =
|
||||
super::super::pyast::NodeExprContext::make_static_type().into();
|
||||
|
||||
for field in &fields {
|
||||
if set_fields.contains(field.as_str()) {
|
||||
continue;
|
||||
}
|
||||
if let Some(ftype) = ft_dict.get_item_opt::<Wtf8>(field.as_wtf8(), vm)? {
|
||||
for field in remaining_fields.elements() {
|
||||
if let Some(ftype) = ft_dict.get_item_opt(&*field, vm)? {
|
||||
if ftype.fast_isinstance(vm.ctx.types.union_type) {
|
||||
// Optional field (T | None) — no default
|
||||
} else if ftype.fast_isinstance(vm.ctx.types.generic_alias_type) {
|
||||
// List field (list[T]) — default to []
|
||||
let empty_list: PyObjectRef = vm.ctx.new_list(vec![]).into();
|
||||
zelf.set_attr(vm.ctx.intern_str(field.as_wtf8()), empty_list, vm)?;
|
||||
ast_set_attr(&zelf, &field, empty_list, vm)?;
|
||||
} else if ftype.is(&expr_ctx_type) {
|
||||
// expr_context — default to Load()
|
||||
let load_type =
|
||||
@@ -445,13 +490,15 @@ Support for arbitrary keyword arguments is deprecated and will be removed in Pyt
|
||||
.unwrap_or_else(|| {
|
||||
vm.ctx.new_base_object(load_type, Some(vm.ctx.new_dict()))
|
||||
});
|
||||
zelf.set_attr(vm.ctx.intern_str(field.as_wtf8()), load_instance, vm)?;
|
||||
ast_set_attr(&zelf, &field, load_instance, vm)?;
|
||||
} else {
|
||||
// Required field missing: emit DeprecationWarning.
|
||||
let field_repr = field.repr(vm)?;
|
||||
let message = vm.ctx.new_str(format!(
|
||||
"{}.__init__ missing 1 required positional argument: '{}'",
|
||||
"{}.__init__ missing 1 required positional argument: {}. \
|
||||
This will become an error in Python 3.15.",
|
||||
zelf.class().name(),
|
||||
field.as_wtf8()
|
||||
field_repr
|
||||
));
|
||||
warn::warn(
|
||||
message.into(),
|
||||
@@ -461,6 +508,21 @@ Support for arbitrary keyword arguments is deprecated and will be removed in Pyt
|
||||
vm,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
let field_repr = field.repr(vm)?;
|
||||
let message = vm.ctx.new_str(format!(
|
||||
"Field {} is missing from {}._field_types. \
|
||||
This will become an error in Python 3.15.",
|
||||
field_repr,
|
||||
zelf.class().name()
|
||||
));
|
||||
warn::warn(
|
||||
message.into(),
|
||||
Some(vm.ctx.exceptions.deprecation_warning.to_owned()),
|
||||
1,
|
||||
None,
|
||||
vm,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,9 +572,29 @@ Support for arbitrary keyword arguments is deprecated and will be removed in Pyt
|
||||
.map_err(|_| vm.new_type_error("AST is not a type"))?;
|
||||
let ctx = &vm.ctx;
|
||||
let empty_tuple = ctx.empty_tuple.clone();
|
||||
let set_empty_annotations = |typ: &Py<PyType>| {
|
||||
typ.set_str_attr("__annotations__", ctx.new_dict(), ctx);
|
||||
};
|
||||
set_empty_annotations(&ast_type);
|
||||
ast_type.set_str_attr("_fields", empty_tuple.clone(), ctx);
|
||||
ast_type.set_str_attr("_attributes", empty_tuple.clone(), ctx);
|
||||
ast_type.set_str_attr("__match_args__", empty_tuple, ctx);
|
||||
for typ in [
|
||||
super::super::pyast::NodeMod::static_type(),
|
||||
super::super::pyast::NodeStmt::static_type(),
|
||||
super::super::pyast::NodeExpr::static_type(),
|
||||
super::super::pyast::NodeExprContext::static_type(),
|
||||
super::super::pyast::NodeBoolOp::static_type(),
|
||||
super::super::pyast::NodeOperator::static_type(),
|
||||
super::super::pyast::NodeUnaryOp::static_type(),
|
||||
super::super::pyast::NodeCmpOp::static_type(),
|
||||
super::super::pyast::NodeExceptHandler::static_type(),
|
||||
super::super::pyast::NodePattern::static_type(),
|
||||
super::super::pyast::NodeTypeIgnore::static_type(),
|
||||
super::super::pyast::NodeTypeParam::static_type(),
|
||||
] {
|
||||
set_empty_annotations(typ);
|
||||
}
|
||||
|
||||
const AST_REDUCE: PyMethodDef = PyMethodDef::new_const(
|
||||
"__reduce__",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::{
|
||||
AsObject, PyObjectRef, PyResult, VirtualMachine,
|
||||
builtins::{PyList, PyTuple},
|
||||
builtins::{PyList, PyStr, PyTuple},
|
||||
class::PyClassImpl,
|
||||
recursion::ReprGuard,
|
||||
stdlib::_ast::NodeAst,
|
||||
};
|
||||
use rustpython_common::wtf8::Wtf8Buf;
|
||||
@@ -33,9 +34,7 @@ fn repr_ast_list(vm: &VirtualMachine, items: Vec<PyObjectRef>, depth: usize) ->
|
||||
rendered.push_wtf8(&parts[0]);
|
||||
}
|
||||
if items.len() > 2 {
|
||||
if !parts[0].is_empty() {
|
||||
rendered.push_wtf8(", ...".as_ref());
|
||||
}
|
||||
rendered.push_wtf8(", ...".as_ref());
|
||||
if parts.len() > 1 {
|
||||
rendered.push_wtf8(", ".as_ref());
|
||||
rendered.push_wtf8(&parts[1]);
|
||||
@@ -75,9 +74,7 @@ fn repr_ast_tuple(vm: &VirtualMachine, items: Vec<PyObjectRef>, depth: usize) ->
|
||||
rendered.push_wtf8(&parts[0]);
|
||||
}
|
||||
if items.len() > 2 {
|
||||
if !parts[0].is_empty() {
|
||||
rendered.push_wtf8(", ...".as_ref());
|
||||
}
|
||||
rendered.push_wtf8(", ...".as_ref());
|
||||
if parts.len() > 1 {
|
||||
rendered.push_wtf8(", ".as_ref());
|
||||
rendered.push_wtf8(&parts[1]);
|
||||
@@ -86,9 +83,6 @@ fn repr_ast_tuple(vm: &VirtualMachine, items: Vec<PyObjectRef>, depth: usize) ->
|
||||
rendered.push_wtf8(", ".as_ref());
|
||||
rendered.push_wtf8(&parts[1]);
|
||||
}
|
||||
if items.len() == 1 {
|
||||
rendered.push_wtf8(",".as_ref());
|
||||
}
|
||||
rendered.push_wtf8(")".as_ref());
|
||||
Ok(rendered)
|
||||
}
|
||||
@@ -104,18 +98,24 @@ pub(crate) fn repr_ast_node(
|
||||
s.push_wtf8("(...)".as_ref());
|
||||
return Ok(s);
|
||||
}
|
||||
let Some(_guard) = ReprGuard::enter(vm, obj.as_object()) else {
|
||||
let mut s = Wtf8Buf::from(&*cls.name());
|
||||
s.push_wtf8("(...)".as_ref());
|
||||
return Ok(s);
|
||||
};
|
||||
|
||||
let fields = cls.get_attr(vm.ctx.intern_str("_fields"));
|
||||
let fields = match fields {
|
||||
Some(fields) => fields.try_to_value::<Vec<crate::builtins::PyStrRef>>(vm)?,
|
||||
let fields = match cls.get_attr(vm.ctx.intern_str("_fields")) {
|
||||
Some(fields) => fields,
|
||||
None => {
|
||||
let mut s = Wtf8Buf::from(&*cls.name());
|
||||
s.push_wtf8("(...)".as_ref());
|
||||
return Ok(s);
|
||||
}
|
||||
};
|
||||
let fields = fields.sequence_unchecked();
|
||||
let numfields = fields.length(vm)?;
|
||||
|
||||
if fields.is_empty() {
|
||||
if numfields == 0 {
|
||||
let mut s = Wtf8Buf::from(&*cls.name());
|
||||
s.push_wtf8("()".as_ref());
|
||||
return Ok(s);
|
||||
@@ -124,8 +124,12 @@ pub(crate) fn repr_ast_node(
|
||||
let mut rendered = Wtf8Buf::from(&*cls.name());
|
||||
rendered.push_wtf8("(".as_ref());
|
||||
|
||||
for (idx, field) in fields.iter().enumerate() {
|
||||
let value = obj.get_attr(field, vm)?;
|
||||
for idx in 0..numfields {
|
||||
let field = fields.get_item(idx as isize, vm)?;
|
||||
let field = field
|
||||
.downcast::<PyStr>()
|
||||
.map_err(|_| vm.new_type_error("attribute name must be string"))?;
|
||||
let value = obj.get_attr(&field, vm)?;
|
||||
let value_repr = if value.fast_isinstance(vm.ctx.types.list_type) {
|
||||
let list = value
|
||||
.downcast::<PyList>()
|
||||
|
||||
+1143
-652
File diff suppressed because it is too large.
Load diff
+424
-133
@@ -19,6 +19,7 @@ fn ruff_fstring_element_into_iter(
|
||||
}
|
||||
|
||||
fn ruff_fstring_element_to_joined_str_part(
|
||||
vm: &VirtualMachine,
|
||||
element: ast::InterpolatedStringElement,
|
||||
) -> JoinedStrPart {
|
||||
match element {
|
||||
@@ -38,12 +39,21 @@ fn ruff_fstring_element_to_joined_str_part(
|
||||
conversion,
|
||||
format_spec,
|
||||
node_index: _,
|
||||
}) => JoinedStrPart::FormattedValue(FormattedValue {
|
||||
value: expression,
|
||||
conversion,
|
||||
format_spec: ruff_format_spec_to_joined_str(format_spec),
|
||||
range,
|
||||
}),
|
||||
runtime_str: _,
|
||||
runtime_interpolation_format_spec: _,
|
||||
runtime_formatted_value_format_spec,
|
||||
}) => {
|
||||
let runtime_format_spec = runtime_formatted_value_format_spec.or_else(|| {
|
||||
ruff_format_spec_to_joined_str(vm, format_spec)
|
||||
.map(|joined_str| Box::new(joined_str.into_expr(false)))
|
||||
});
|
||||
JoinedStrPart::FormattedValue(FormattedValue {
|
||||
value: expression,
|
||||
conversion,
|
||||
format_spec: runtime_format_spec,
|
||||
range,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,6 +245,7 @@ fn warn_invalid_escape_sequences_in_format_spec(
|
||||
}
|
||||
|
||||
fn ruff_format_spec_to_joined_str(
|
||||
vm: &VirtualMachine,
|
||||
format_spec: Option<Box<ast::InterpolatedStringFormatSpec>>,
|
||||
) -> Option<Box<JoinedStr>> {
|
||||
match format_spec {
|
||||
@@ -254,10 +265,14 @@ fn ruff_format_spec_to_joined_str(
|
||||
range
|
||||
};
|
||||
let values: Vec<_> = ruff_fstring_element_into_iter(elements)
|
||||
.map(ruff_fstring_element_to_joined_str_part)
|
||||
.map(|element| ruff_fstring_element_to_joined_str_part(vm, element))
|
||||
.collect();
|
||||
let values = normalize_joined_str_parts(values).into_boxed_slice();
|
||||
Some(Box::new(JoinedStr { range, values }))
|
||||
Some(Box::new(JoinedStr {
|
||||
range,
|
||||
values,
|
||||
runtime_values: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,40 +305,98 @@ fn ruff_fstring_element_to_ruff_fstring_part(
|
||||
}
|
||||
}
|
||||
|
||||
fn joined_str_to_ruff_format_spec(
|
||||
joined_str: Option<Box<JoinedStr>>,
|
||||
fn format_spec_expr_to_ruff_format_spec(
|
||||
format_spec: Option<Box<ast::Expr>>,
|
||||
) -> Option<Box<ast::InterpolatedStringFormatSpec>> {
|
||||
match joined_str {
|
||||
None => None,
|
||||
Some(joined_str) => {
|
||||
let JoinedStr { range, values } = *joined_str;
|
||||
let elements: Vec<_> = Box::into_iter(values)
|
||||
.map(joined_str_part_to_ruff_fstring_element)
|
||||
.collect();
|
||||
let format_spec = ast::InterpolatedStringFormatSpec {
|
||||
node_index: Default::default(),
|
||||
let format_spec = format_spec?;
|
||||
let ast::Expr::FString(mut fstring) = *format_spec else {
|
||||
return None;
|
||||
};
|
||||
let ast::ExprFString {
|
||||
range,
|
||||
ref mut value,
|
||||
node_index: _,
|
||||
runtime_joined_str: _,
|
||||
runtime_values: _,
|
||||
} = fstring;
|
||||
let default_part = ast::FStringPart::FString(ast::FString {
|
||||
node_index: Default::default(),
|
||||
range: Default::default(),
|
||||
elements: Default::default(),
|
||||
flags: ast::FStringFlags::empty(),
|
||||
});
|
||||
let mut elements = Vec::new();
|
||||
for i in 0..value.as_slice().len() {
|
||||
let part = core::mem::replace(value.iter_mut().nth(i).unwrap(), default_part.clone());
|
||||
match part {
|
||||
ast::FStringPart::Literal(ast::StringLiteral {
|
||||
range,
|
||||
elements: elements.into(),
|
||||
};
|
||||
Some(Box::new(format_spec))
|
||||
value,
|
||||
node_index: _,
|
||||
flags: _,
|
||||
}) => elements.push(ast::InterpolatedStringElement::Literal(
|
||||
ast::InterpolatedStringLiteralElement {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
value,
|
||||
},
|
||||
)),
|
||||
ast::FStringPart::FString(ast::FString {
|
||||
elements: fstring_elements,
|
||||
..
|
||||
}) => {
|
||||
elements.extend(ruff_fstring_element_into_iter(fstring_elements));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Box::new(ast::InterpolatedStringFormatSpec {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
elements: elements.into(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct JoinedStr {
|
||||
pub(super) range: TextRange,
|
||||
pub(super) values: Box<[JoinedStrPart]>,
|
||||
pub(super) runtime_values: Option<Vec<Option<ast::Expr>>>,
|
||||
}
|
||||
|
||||
impl JoinedStr {
|
||||
pub(super) fn into_expr(self) -> ast::Expr {
|
||||
let Self { range, values } = self;
|
||||
pub(super) fn into_expr(self, from_ast_object: bool) -> ast::Expr {
|
||||
let Self {
|
||||
range,
|
||||
values,
|
||||
runtime_values: mut raw_runtime_values,
|
||||
} = self;
|
||||
let values = if values.iter().any(joined_str_part_requires_runtime_values) {
|
||||
if raw_runtime_values.is_none() {
|
||||
raw_runtime_values = Some(
|
||||
values
|
||||
.into_vec()
|
||||
.into_iter()
|
||||
.map(|part| joined_str_part_to_expr(from_ast_object, part))
|
||||
.map(Some)
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
Vec::new().into_boxed_slice()
|
||||
} else {
|
||||
values
|
||||
};
|
||||
let (runtime_joined_str, runtime_values) =
|
||||
raw_runtime_values.take().map_or((None, None), |values| {
|
||||
if values.iter().any(Option::is_none) {
|
||||
(None, Some(values))
|
||||
} else {
|
||||
(Some(values.into_iter().flatten().collect()), None)
|
||||
}
|
||||
});
|
||||
ast::Expr::FString(ast::ExprFString {
|
||||
node_index: Default::default(),
|
||||
range: Default::default(),
|
||||
range,
|
||||
value: match values.len() {
|
||||
// ruff represents an empty fstring like this:
|
||||
0 => ast::FStringValue::single(ast::FString {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
@@ -332,7 +405,8 @@ impl JoinedStr {
|
||||
}),
|
||||
1 => ast::FStringValue::single(
|
||||
Box::<[_]>::into_iter(values)
|
||||
.map(joined_str_part_to_ruff_fstring_element)
|
||||
.map(|part| joined_str_part_to_ruff_fstring_element(from_ast_object, part))
|
||||
.map(Option::unwrap)
|
||||
.map(|element| ast::FString {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
@@ -344,54 +418,108 @@ impl JoinedStr {
|
||||
),
|
||||
_ => ast::FStringValue::concatenated(
|
||||
Box::<[_]>::into_iter(values)
|
||||
.map(joined_str_part_to_ruff_fstring_element)
|
||||
.map(|part| joined_str_part_to_ruff_fstring_element(from_ast_object, part))
|
||||
.map(Option::unwrap)
|
||||
.map(ruff_fstring_element_to_ruff_fstring_part)
|
||||
.collect(),
|
||||
),
|
||||
},
|
||||
runtime_joined_str,
|
||||
runtime_values,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn joined_str_part_to_ruff_fstring_element(part: JoinedStrPart) -> ast::InterpolatedStringElement {
|
||||
fn joined_str_part_requires_runtime_values(part: &JoinedStrPart) -> bool {
|
||||
matches!(
|
||||
part,
|
||||
JoinedStrPart::Constant(Constant {
|
||||
value,
|
||||
..
|
||||
}) if !matches!(value, ConstantLiteral::Str { .. })
|
||||
)
|
||||
}
|
||||
|
||||
fn joined_str_part_to_expr(from_ast_object: bool, part: JoinedStrPart) -> ast::Expr {
|
||||
match part {
|
||||
JoinedStrPart::FormattedValue(value) => formatted_value_to_expr(from_ast_object, value),
|
||||
JoinedStrPart::Constant(value) => value.into_expr(),
|
||||
}
|
||||
}
|
||||
|
||||
fn joined_str_part_to_ruff_fstring_element(
|
||||
from_ast_object: bool,
|
||||
part: JoinedStrPart,
|
||||
) -> Option<ast::InterpolatedStringElement> {
|
||||
match part {
|
||||
JoinedStrPart::FormattedValue(value) => {
|
||||
ast::InterpolatedStringElement::Interpolation(ast::InterpolatedElement {
|
||||
node_index: Default::default(),
|
||||
range: value.range,
|
||||
expression: value.value.clone(),
|
||||
debug_text: None, // TODO: What is this?
|
||||
conversion: value.conversion,
|
||||
format_spec: joined_str_to_ruff_format_spec(value.format_spec),
|
||||
})
|
||||
let format_spec = value.format_spec.clone();
|
||||
let runtime_formatted_value_format_spec = (from_ast_object && format_spec.is_some())
|
||||
.then_some(format_spec.clone())
|
||||
.flatten();
|
||||
Some(ast::InterpolatedStringElement::Interpolation(
|
||||
ast::InterpolatedElement {
|
||||
node_index: Default::default(),
|
||||
range: value.range,
|
||||
expression: value.value.clone(),
|
||||
debug_text: None,
|
||||
conversion: value.conversion,
|
||||
format_spec: format_spec_expr_to_ruff_format_spec(format_spec),
|
||||
runtime_str: None,
|
||||
runtime_interpolation_format_spec: None,
|
||||
runtime_formatted_value_format_spec,
|
||||
},
|
||||
))
|
||||
}
|
||||
JoinedStrPart::Constant(value) => {
|
||||
ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement {
|
||||
node_index: Default::default(),
|
||||
range: value.range,
|
||||
value: match value.value {
|
||||
ConstantLiteral::Str { value, .. } => value,
|
||||
_ => todo!(),
|
||||
let Constant { range, value, .. } = value;
|
||||
let ConstantLiteral::Str { value, .. } = value else {
|
||||
return None;
|
||||
};
|
||||
Some(ast::InterpolatedStringElement::Literal(
|
||||
ast::InterpolatedStringLiteralElement {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
value,
|
||||
},
|
||||
})
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
pub(super) fn joined_str_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<JoinedStr> {
|
||||
let values: Vec<Option<ast::Expr>> =
|
||||
get_node_list_field(vm, source_file, &object, "values", "JoinedStr")?;
|
||||
Ok(JoinedStr {
|
||||
values: Vec::new().into_boxed_slice(),
|
||||
runtime_values: Some(values),
|
||||
range,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for JoinedStr {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self { values, range } = self;
|
||||
let Self {
|
||||
values,
|
||||
runtime_values,
|
||||
range,
|
||||
} = self;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodeExprJoinedStr::static_type().to_owned())
|
||||
.unwrap();
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item(
|
||||
"values",
|
||||
BoxedSlice(values).ast_to_object(vm, source_file),
|
||||
vm,
|
||||
)
|
||||
.unwrap();
|
||||
let values = if let Some(runtime_values) = runtime_values {
|
||||
BoxedSlice(runtime_values.into_boxed_slice()).ast_to_object(vm, source_file)
|
||||
} else {
|
||||
BoxedSlice(values).ast_to_object(vm, source_file)
|
||||
};
|
||||
dict.set_item("values", values, vm).unwrap();
|
||||
node_add_location(&dict, range, vm, source_file);
|
||||
node.into()
|
||||
}
|
||||
@@ -400,15 +528,8 @@ impl Node for JoinedStr {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let values: BoxedSlice<_> = Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "values", "JoinedStr")?,
|
||||
)?;
|
||||
Ok(Self {
|
||||
values: values.0,
|
||||
range: range_from_object(vm, source_file, object, "JoinedStr")?,
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "JoinedStr")?;
|
||||
joined_str_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,8 +552,7 @@ impl Node for JoinedStrPart {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let cls = object.class();
|
||||
if cls.is(pyast::NodeExprFormattedValue::static_type()) {
|
||||
if is_node_instance(vm, &object, pyast::NodeExprFormattedValue::static_type())? {
|
||||
Ok(Self::FormattedValue(Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
@@ -452,11 +572,31 @@ impl Node for JoinedStrPart {
|
||||
pub(super) struct FormattedValue {
|
||||
value: Box<ast::Expr>,
|
||||
conversion: ast::ConversionFlag,
|
||||
format_spec: Option<Box<JoinedStr>>,
|
||||
format_spec: Option<Box<ast::Expr>>,
|
||||
range: TextRange,
|
||||
}
|
||||
|
||||
// constructor
|
||||
pub(super) fn formatted_value_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<FormattedValue> {
|
||||
Ok(FormattedValue {
|
||||
value: get_required_node_field(vm, source_file, &object, "value", "FormattedValue")?,
|
||||
conversion: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "conversion", "FormattedValue")?,
|
||||
)?,
|
||||
format_spec: get_node_field_opt(vm, &object, "format_spec")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
range,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for FormattedValue {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
@@ -487,25 +627,24 @@ impl Node for FormattedValue {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
value: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "value", "FormattedValue")?,
|
||||
)?,
|
||||
conversion: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "conversion", "FormattedValue")?,
|
||||
)?,
|
||||
format_spec: get_node_field_opt(vm, &object, "format_spec")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
range: range_from_object(vm, source_file, object, "FormattedValue")?,
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "FormattedValue")?;
|
||||
formatted_value_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn formatted_value_to_expr(
|
||||
from_ast_object: bool,
|
||||
formatted: FormattedValue,
|
||||
) -> ast::Expr {
|
||||
let range = formatted.range;
|
||||
JoinedStr {
|
||||
range,
|
||||
values: vec![JoinedStrPart::FormattedValue(formatted)].into_boxed_slice(),
|
||||
runtime_values: None,
|
||||
}
|
||||
.into_expr(from_ast_object)
|
||||
}
|
||||
|
||||
pub(super) fn fstring_to_object(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
@@ -515,7 +654,27 @@ pub(super) fn fstring_to_object(
|
||||
range,
|
||||
mut value,
|
||||
node_index: _,
|
||||
runtime_joined_str,
|
||||
runtime_values,
|
||||
} = expression;
|
||||
if let Some(joined_str) = runtime_joined_str {
|
||||
return JoinedStr {
|
||||
range,
|
||||
values: Vec::new().into_boxed_slice(),
|
||||
runtime_values: Some(joined_str.into_iter().map(Some).collect()),
|
||||
}
|
||||
.ast_to_object(vm, source_file);
|
||||
}
|
||||
|
||||
if let Some(values) = runtime_values {
|
||||
return JoinedStr {
|
||||
range,
|
||||
values: Vec::new().into_boxed_slice(),
|
||||
runtime_values: Some(values),
|
||||
}
|
||||
.ast_to_object(vm, source_file);
|
||||
}
|
||||
|
||||
let default_part = ast::FStringPart::FString(ast::FString {
|
||||
node_index: Default::default(),
|
||||
range: Default::default(),
|
||||
@@ -545,7 +704,7 @@ pub(super) fn fstring_to_object(
|
||||
node_index: _,
|
||||
}) => {
|
||||
for element in ruff_fstring_element_into_iter(elements) {
|
||||
values.push(ruff_fstring_element_to_joined_str_part(element));
|
||||
values.push(ruff_fstring_element_to_joined_str_part(vm, element));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -555,12 +714,13 @@ pub(super) fn fstring_to_object(
|
||||
if let JoinedStrPart::FormattedValue(value) = part
|
||||
&& let Some(format_spec) = &value.format_spec
|
||||
{
|
||||
warn_invalid_escape_sequences_in_format_spec(vm, source_file, format_spec.range);
|
||||
warn_invalid_escape_sequences_in_format_spec(vm, source_file, format_spec.range());
|
||||
}
|
||||
}
|
||||
let c = JoinedStr {
|
||||
range,
|
||||
values: values.into_boxed_slice(),
|
||||
runtime_values: None,
|
||||
};
|
||||
c.ast_to_object(vm, source_file)
|
||||
}
|
||||
@@ -568,8 +728,9 @@ pub(super) fn fstring_to_object(
|
||||
// ===== TString (Template String) Support =====
|
||||
|
||||
fn ruff_tstring_element_to_template_str_part(
|
||||
element: ast::InterpolatedStringElement,
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
element: ast::InterpolatedStringElement,
|
||||
) -> TemplateStrPart {
|
||||
match element {
|
||||
ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement {
|
||||
@@ -588,6 +749,9 @@ fn ruff_tstring_element_to_template_str_part(
|
||||
conversion,
|
||||
format_spec,
|
||||
node_index: _,
|
||||
runtime_str,
|
||||
runtime_interpolation_format_spec,
|
||||
runtime_formatted_value_format_spec: _,
|
||||
}) => {
|
||||
let expr_range =
|
||||
extend_expr_range_with_wrapping_parens(source_file, range, expression.range())
|
||||
@@ -604,11 +768,23 @@ fn ruff_tstring_element_to_template_str_part(
|
||||
} else {
|
||||
tstring_interpolation_expr_str(source_file, range, expr_range)
|
||||
};
|
||||
let runtime_interpolation = super::constant::runtime_interpolation_object(
|
||||
vm,
|
||||
runtime_str,
|
||||
runtime_interpolation_format_spec,
|
||||
);
|
||||
TemplateStrPart::Interpolation(TStringInterpolation {
|
||||
value: expression,
|
||||
str: expr_str,
|
||||
str: runtime_interpolation
|
||||
.as_ref()
|
||||
.map_or_else(|| vm.ctx.new_str(expr_str).into(), |(str, _)| str.clone()),
|
||||
conversion,
|
||||
format_spec: ruff_format_spec_to_joined_str(format_spec),
|
||||
format_spec: runtime_interpolation
|
||||
.and_then(|(_, format_spec)| format_spec)
|
||||
.or_else(|| {
|
||||
ruff_format_spec_to_joined_str(vm, format_spec)
|
||||
.map(|joined_str| Box::new(joined_str.into_expr(false)))
|
||||
}),
|
||||
range,
|
||||
})
|
||||
}
|
||||
@@ -695,34 +871,51 @@ fn strip_interpolation_expr(expr_source: &str) -> String {
|
||||
pub(super) struct TemplateStr {
|
||||
pub(super) range: TextRange,
|
||||
pub(super) values: Box<[TemplateStrPart]>,
|
||||
pub(super) runtime_values: Option<Vec<Option<ast::Expr>>>,
|
||||
}
|
||||
|
||||
pub(super) fn template_str_to_expr(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
template: TemplateStr,
|
||||
) -> PyResult<ast::Expr> {
|
||||
let TemplateStr { range, values } = template;
|
||||
let elements = template_parts_to_elements(vm, values)?;
|
||||
let TemplateStr {
|
||||
range,
|
||||
values,
|
||||
runtime_values: raw_runtime_values,
|
||||
} = template;
|
||||
let elements = template_parts_to_elements(vm, source_file, values)?;
|
||||
let tstring = ast::TString {
|
||||
range,
|
||||
node_index: Default::default(),
|
||||
elements,
|
||||
flags: ast::TStringFlags::empty(),
|
||||
};
|
||||
let (runtime_template_str, runtime_values) =
|
||||
raw_runtime_values.map_or((None, None), |values| {
|
||||
if values.iter().any(Option::is_none) {
|
||||
(None, Some(values))
|
||||
} else {
|
||||
(Some(values.into_iter().flatten().collect()), None)
|
||||
}
|
||||
});
|
||||
Ok(ast::Expr::TString(ast::ExprTString {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
value: ast::TStringValue::single(tstring),
|
||||
runtime_template_str,
|
||||
runtime_values,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) fn interpolation_to_expr(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
interpolation: TStringInterpolation,
|
||||
) -> PyResult<ast::Expr> {
|
||||
let range = interpolation.range;
|
||||
let part = TemplateStrPart::Interpolation(interpolation);
|
||||
let elements = template_parts_to_elements(vm, vec![part].into_boxed_slice())?;
|
||||
let range = TextRange::default();
|
||||
let elements = template_parts_to_elements(vm, source_file, vec![part].into_boxed_slice())?;
|
||||
let tstring = ast::TString {
|
||||
range,
|
||||
node_index: Default::default(),
|
||||
@@ -733,22 +926,26 @@ pub(super) fn interpolation_to_expr(
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
value: ast::TStringValue::single(tstring),
|
||||
runtime_template_str: None,
|
||||
runtime_values: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn template_parts_to_elements(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
values: Box<[TemplateStrPart]>,
|
||||
) -> PyResult<ast::InterpolatedStringElements> {
|
||||
let mut elements = Vec::with_capacity(values.len());
|
||||
for value in values.into_vec() {
|
||||
elements.push(template_part_to_element(vm, value)?);
|
||||
elements.push(template_part_to_element(vm, source_file, value)?);
|
||||
}
|
||||
Ok(ast::InterpolatedStringElements::from(elements))
|
||||
}
|
||||
|
||||
fn template_part_to_element(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
part: TemplateStrPart,
|
||||
) -> PyResult<ast::InterpolatedStringElement> {
|
||||
match part {
|
||||
@@ -767,12 +964,18 @@ fn template_part_to_element(
|
||||
TemplateStrPart::Interpolation(interpolation) => {
|
||||
let TStringInterpolation {
|
||||
value,
|
||||
str,
|
||||
conversion,
|
||||
format_spec,
|
||||
range,
|
||||
..
|
||||
} = interpolation;
|
||||
let format_spec = joined_str_to_ruff_format_spec(format_spec);
|
||||
let str_constant =
|
||||
super::constant::constant_object_to_constant_data(vm, source_file, str)?;
|
||||
let runtime_str = Some(super::constant::constant_data_to_ast_constant_value(
|
||||
str_constant,
|
||||
));
|
||||
let runtime_interpolation_format_spec = format_spec.clone();
|
||||
let format_spec = format_spec_expr_to_ruff_format_spec(format_spec);
|
||||
Ok(ast::InterpolatedStringElement::Interpolation(
|
||||
ast::InterpolatedElement {
|
||||
range,
|
||||
@@ -781,6 +984,9 @@ fn template_part_to_element(
|
||||
debug_text: None,
|
||||
conversion,
|
||||
format_spec,
|
||||
runtime_str,
|
||||
runtime_interpolation_format_spec,
|
||||
runtime_formatted_value_format_spec: None,
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -788,19 +994,38 @@ fn template_part_to_element(
|
||||
}
|
||||
|
||||
// constructor
|
||||
pub(super) fn template_str_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<TemplateStr> {
|
||||
let values: Vec<Option<ast::Expr>> =
|
||||
get_node_list_field(vm, source_file, &object, "values", "TemplateStr")?;
|
||||
Ok(TemplateStr {
|
||||
values: Vec::new().into_boxed_slice(),
|
||||
runtime_values: Some(values),
|
||||
range,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for TemplateStr {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self { values, range } = self;
|
||||
let Self {
|
||||
values,
|
||||
runtime_values,
|
||||
range,
|
||||
} = self;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(vm, pyast::NodeExprTemplateStr::static_type().to_owned())
|
||||
.unwrap();
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item(
|
||||
"values",
|
||||
BoxedSlice(values).ast_to_object(vm, source_file),
|
||||
vm,
|
||||
)
|
||||
.unwrap();
|
||||
let values = if let Some(runtime_values) = runtime_values {
|
||||
BoxedSlice(runtime_values.into_boxed_slice()).ast_to_object(vm, source_file)
|
||||
} else {
|
||||
BoxedSlice(values).ast_to_object(vm, source_file)
|
||||
};
|
||||
dict.set_item("values", values, vm).unwrap();
|
||||
node_add_location(&dict, range, vm, source_file);
|
||||
node.into()
|
||||
}
|
||||
@@ -809,15 +1034,8 @@ impl Node for TemplateStr {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let values: BoxedSlice<_> = Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "values", "TemplateStr")?,
|
||||
)?;
|
||||
Ok(Self {
|
||||
values: values.0,
|
||||
range: range_from_object(vm, source_file, object, "TemplateStr")?,
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "TemplateStr")?;
|
||||
template_str_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -840,8 +1058,7 @@ impl Node for TemplateStrPart {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let cls = object.class();
|
||||
if cls.is(pyast::NodeExprInterpolation::static_type()) {
|
||||
if is_node_instance(vm, &object, pyast::NodeExprInterpolation::static_type())? {
|
||||
Ok(Self::Interpolation(Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
@@ -860,13 +1077,38 @@ impl Node for TemplateStrPart {
|
||||
#[derive(Debug)]
|
||||
pub(super) struct TStringInterpolation {
|
||||
value: Box<ast::Expr>,
|
||||
str: String,
|
||||
str: PyObjectRef,
|
||||
conversion: ast::ConversionFlag,
|
||||
format_spec: Option<Box<JoinedStr>>,
|
||||
format_spec: Option<Box<ast::Expr>>,
|
||||
range: TextRange,
|
||||
}
|
||||
|
||||
// constructor
|
||||
pub(super) fn tstring_interpolation_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<TStringInterpolation> {
|
||||
let value = get_required_node_field(vm, source_file, &object, "value", "Interpolation")?;
|
||||
let str = get_node_field(vm, &object, "str", "Interpolation")?;
|
||||
let conversion = Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "conversion", "Interpolation")?,
|
||||
)?;
|
||||
let format_spec: Option<Box<ast::Expr>> = get_node_field_opt(vm, &object, "format_spec")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?;
|
||||
Ok(TStringInterpolation {
|
||||
value,
|
||||
str,
|
||||
conversion,
|
||||
format_spec,
|
||||
range,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for TStringInterpolation {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
@@ -882,8 +1124,7 @@ impl Node for TStringInterpolation {
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item("value", value.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
dict.set_item("str", vm.ctx.new_str(str).into(), vm)
|
||||
.unwrap();
|
||||
dict.set_item("str", str, vm).unwrap();
|
||||
dict.set_item("conversion", conversion.ast_to_object(vm, source_file), vm)
|
||||
.unwrap();
|
||||
dict.set_item(
|
||||
@@ -900,25 +1141,8 @@ impl Node for TStringInterpolation {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let str_obj = get_node_field(vm, &object, "str", "Interpolation")?;
|
||||
let str_val: String = str_obj.try_into_value(vm)?;
|
||||
Ok(Self {
|
||||
value: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "value", "Interpolation")?,
|
||||
)?,
|
||||
str: str_val,
|
||||
conversion: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "conversion", "Interpolation")?,
|
||||
)?,
|
||||
format_spec: get_node_field_opt(vm, &object, "format_spec")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
range: range_from_object(vm, source_file, object, "Interpolation")?,
|
||||
})
|
||||
let range = range_from_object(vm, source_file, object.clone(), "Interpolation")?;
|
||||
tstring_interpolation_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -931,7 +1155,42 @@ pub(super) fn tstring_to_object(
|
||||
range,
|
||||
mut value,
|
||||
node_index: _,
|
||||
runtime_template_str,
|
||||
runtime_values,
|
||||
} = expression;
|
||||
if let Some(template_str) = runtime_template_str {
|
||||
return TemplateStr {
|
||||
range,
|
||||
values: Vec::new().into_boxed_slice(),
|
||||
runtime_values: Some(template_str.into_iter().map(Some).collect()),
|
||||
}
|
||||
.ast_to_object(vm, source_file);
|
||||
}
|
||||
|
||||
if let Some(values) = runtime_values {
|
||||
return TemplateStr {
|
||||
range,
|
||||
values: Vec::new().into_boxed_slice(),
|
||||
runtime_values: Some(values),
|
||||
}
|
||||
.ast_to_object(vm, source_file);
|
||||
}
|
||||
|
||||
if let [tstring] = value.as_slice()
|
||||
&& let Some(ast::InterpolatedStringElement::Interpolation(interp)) =
|
||||
tstring.elements.iter().next()
|
||||
&& tstring.elements.get(1).is_none()
|
||||
&& let Some((str, format_spec)) = super::constant::runtime_interpolation_object(
|
||||
vm,
|
||||
interp.runtime_str.clone(),
|
||||
interp.runtime_interpolation_format_spec.clone(),
|
||||
)
|
||||
&& let Some(interpolation) =
|
||||
standalone_tstring_interpolation_to_object(vm, source_file, &value, str, format_spec)
|
||||
{
|
||||
return interpolation;
|
||||
}
|
||||
|
||||
let default_tstring = ast::TString {
|
||||
node_index: Default::default(),
|
||||
range: Default::default(),
|
||||
@@ -943,8 +1202,9 @@ pub(super) fn tstring_to_object(
|
||||
let tstring = core::mem::replace(value.iter_mut().nth(i).unwrap(), default_tstring.clone());
|
||||
for element in ruff_fstring_element_into_iter(tstring.elements) {
|
||||
values.push(ruff_tstring_element_to_template_str_part(
|
||||
element,
|
||||
vm,
|
||||
source_file,
|
||||
element,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -952,6 +1212,37 @@ pub(super) fn tstring_to_object(
|
||||
let c = TemplateStr {
|
||||
range,
|
||||
values: values.into_boxed_slice(),
|
||||
runtime_values: None,
|
||||
};
|
||||
c.ast_to_object(vm, source_file)
|
||||
}
|
||||
|
||||
fn standalone_tstring_interpolation_to_object(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
value: &ast::TStringValue,
|
||||
str: PyObjectRef,
|
||||
format_spec: Option<Box<ast::Expr>>,
|
||||
) -> Option<PyObjectRef> {
|
||||
let [tstring] = value.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
let mut elements = tstring.elements.iter();
|
||||
let ast::InterpolatedStringElement::Interpolation(interp) = elements.next()? else {
|
||||
return None;
|
||||
};
|
||||
if elements.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
let interpolation = TStringInterpolation {
|
||||
value: interp.expression.clone(),
|
||||
str,
|
||||
conversion: interp.conversion,
|
||||
format_spec: format_spec.or_else(|| {
|
||||
ruff_format_spec_to_joined_str(vm, interp.format_spec.clone())
|
||||
.map(|joined_str| Box::new(joined_str.into_expr(false)))
|
||||
}),
|
||||
range: interp.range,
|
||||
};
|
||||
Some(interpolation.ast_to_object(vm, source_file))
|
||||
}
|
||||
@@ -2,6 +2,7 @@ use super::*;
|
||||
use rustpython_compiler_core::SourceFile;
|
||||
|
||||
pub(super) enum TypeIgnore {
|
||||
None,
|
||||
TypeIgnore(TypeIgnoreTypeIgnore),
|
||||
}
|
||||
|
||||
@@ -9,6 +10,7 @@ pub(super) enum TypeIgnore {
|
||||
impl Node for TypeIgnore {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
match self {
|
||||
Self::None => vm.ctx.none(),
|
||||
Self::TypeIgnore(cons) => cons.ast_to_object(vm, source_file),
|
||||
}
|
||||
}
|
||||
@@ -17,8 +19,9 @@ impl Node for TypeIgnore {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let cls = object.class();
|
||||
Ok(if cls.is(pyast::NodeTypeIgnoreTypeIgnore::static_type()) {
|
||||
Ok(if vm.is_none(&object) {
|
||||
Self::None
|
||||
} else if is_node_instance(vm, &object, pyast::NodeTypeIgnoreTypeIgnore::static_type())? {
|
||||
Self::TypeIgnore(TypeIgnoreTypeIgnore::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
@@ -34,15 +37,14 @@ impl Node for TypeIgnore {
|
||||
}
|
||||
|
||||
pub(super) struct TypeIgnoreTypeIgnore {
|
||||
range: TextRange,
|
||||
lineno: PyRefExact<PyInt>,
|
||||
tag: PyRefExact<PyStr>,
|
||||
lineno: i32,
|
||||
tag: PyObjectRef,
|
||||
}
|
||||
|
||||
// constructor
|
||||
impl Node for TypeIgnoreTypeIgnore {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self { lineno, tag, range } = self;
|
||||
fn ast_to_object(self, vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self { lineno, tag } = self;
|
||||
let node = NodeAst
|
||||
.into_ref_with_type(
|
||||
vm,
|
||||
@@ -50,25 +52,20 @@ impl Node for TypeIgnoreTypeIgnore {
|
||||
)
|
||||
.unwrap();
|
||||
let dict = node.as_object().dict().unwrap();
|
||||
dict.set_item("lineno", lineno.to_pyobject(vm), vm).unwrap();
|
||||
dict.set_item("tag", tag.to_pyobject(vm), vm).unwrap();
|
||||
node_add_location(&dict, range, vm, source_file);
|
||||
dict.set_item("lineno", vm.ctx.new_int(lineno).into(), vm)
|
||||
.unwrap();
|
||||
dict.set_item("tag", tag, vm).unwrap();
|
||||
node.into()
|
||||
}
|
||||
|
||||
fn ast_from_object(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
_source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
lineno: get_node_field(vm, &object, "lineno", "TypeIgnore")?
|
||||
.downcast_exact(vm)
|
||||
.unwrap(),
|
||||
tag: get_node_field(vm, &object, "tag", "TypeIgnore")?
|
||||
.downcast_exact(vm)
|
||||
.unwrap(),
|
||||
range: range_from_object(vm, source_file, object, "TypeIgnore")?,
|
||||
lineno: get_int_field(vm, &object, "lineno", "TypeIgnore")?,
|
||||
tag: node_object_to_ast_string(vm, get_node_field(vm, &object, "tag", "TypeIgnore")?)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,10 @@ use rustpython_compiler_core::SourceFile;
|
||||
|
||||
impl Node for ast::TypeParams {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
self.type_params.ast_to_object(vm, source_file)
|
||||
self.runtime_type_params.map_or_else(
|
||||
|| self.type_params.ast_to_object(vm, source_file),
|
||||
|values| values.ast_to_object(vm, source_file),
|
||||
)
|
||||
}
|
||||
|
||||
fn ast_from_object(
|
||||
@@ -11,22 +14,51 @@ impl Node for ast::TypeParams {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let type_params: Vec<ast::TypeParam> = Node::ast_from_object(vm, source_file, object)?;
|
||||
let range = Option::zip(type_params.first(), type_params.last())
|
||||
.map(|(first, last)| first.range().cover(last.range()))
|
||||
.unwrap_or_default();
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
type_params,
|
||||
range,
|
||||
})
|
||||
Ok(type_params_from_values(
|
||||
vm,
|
||||
Node::ast_from_object(vm, source_file, object)?,
|
||||
))
|
||||
}
|
||||
|
||||
fn is_none(&self) -> bool {
|
||||
self.type_params.is_empty()
|
||||
self.type_params.is_empty() && self.runtime_type_params.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn type_params_from_field(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: &PyObject,
|
||||
field: &'static str,
|
||||
typ: &str,
|
||||
) -> PyResult<Option<Box<ast::TypeParams>>> {
|
||||
let type_params: Vec<Option<ast::TypeParam>> =
|
||||
get_node_list_field(vm, source_file, object, field, typ)?;
|
||||
let type_params = type_params_from_values(vm, type_params);
|
||||
Ok((!type_params.is_none()).then_some(Box::new(type_params)))
|
||||
}
|
||||
|
||||
fn type_params_from_values(
|
||||
_vm: &VirtualMachine,
|
||||
values: Vec<Option<ast::TypeParam>>,
|
||||
) -> ast::TypeParams {
|
||||
let runtime_type_params = values.iter().any(Option::is_none).then(|| values.clone());
|
||||
let type_params = lower_nullable_type_params(&values);
|
||||
let range = Option::zip(type_params.first(), type_params.last())
|
||||
.map(|(first, last)| first.range().cover(last.range()))
|
||||
.unwrap_or_default();
|
||||
ast::TypeParams {
|
||||
node_index: Default::default(),
|
||||
type_params,
|
||||
range,
|
||||
runtime_type_params,
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_nullable_type_params(values: &[Option<ast::TypeParam>]) -> Vec<ast::TypeParam> {
|
||||
values.iter().filter_map(Clone::clone).collect()
|
||||
}
|
||||
|
||||
// sum
|
||||
impl Node for ast::TypeParam {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
@@ -42,35 +74,70 @@ impl Node for ast::TypeParam {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
let cls = object.class();
|
||||
Ok(if cls.is(pyast::NodeTypeParamTypeVar::static_type()) {
|
||||
Self::TypeVar(ast::TypeParamTypeVar::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
} else if cls.is(pyast::NodeTypeParamParamSpec::static_type()) {
|
||||
Self::ParamSpec(ast::TypeParamParamSpec::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
} else if cls.is(pyast::NodeTypeParamTypeVarTuple::static_type()) {
|
||||
Self::TypeVarTuple(ast::TypeParamTypeVarTuple::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
)?)
|
||||
if vm.is_none(&object) {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of type_param, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
}
|
||||
enum TypeParamKind {
|
||||
TypeVar,
|
||||
ParamSpec,
|
||||
TypeVarTuple,
|
||||
}
|
||||
let kind = if is_node_instance(vm, &object, pyast::NodeTypeParamTypeVar::static_type())? {
|
||||
TypeParamKind::TypeVar
|
||||
} else if is_node_instance(vm, &object, pyast::NodeTypeParamParamSpec::static_type())? {
|
||||
TypeParamKind::ParamSpec
|
||||
} else if is_node_instance(vm, &object, pyast::NodeTypeParamTypeVarTuple::static_type())? {
|
||||
TypeParamKind::TypeVarTuple
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected some sort of type_param, but got {}",
|
||||
object.repr(vm)?
|
||||
)));
|
||||
};
|
||||
let range = type_param_range_from_object(vm, source_file, object.clone())?;
|
||||
Ok(match kind {
|
||||
TypeParamKind::TypeVar => Self::TypeVar(type_var_from_object_with_range(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
range,
|
||||
)?),
|
||||
TypeParamKind::ParamSpec => Self::ParamSpec(param_spec_from_object_with_range(
|
||||
vm,
|
||||
source_file,
|
||||
object,
|
||||
range,
|
||||
)?),
|
||||
TypeParamKind::TypeVarTuple => Self::TypeVarTuple(
|
||||
type_var_tuple_from_object_with_range(vm, source_file, object, range)?,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
fn type_var_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<ast::TypeParamTypeVar> {
|
||||
Ok(ast::TypeParamTypeVar {
|
||||
node_index: Default::default(),
|
||||
name: get_required_identifier_field(vm, source_file, &object, "name", "TypeVar")?,
|
||||
bound: get_node_field_opt(vm, &object, "bound")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
default: get_node_field_opt(vm, &object, "default_value")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
range,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for ast::TypeParamTypeVar {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
@@ -99,27 +166,28 @@ impl Node for ast::TypeParamTypeVar {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
name: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "name", "TypeVar")?,
|
||||
)?,
|
||||
bound: get_node_field_opt(vm, &object, "bound")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
default: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "default_value", "TypeVar")?,
|
||||
)?,
|
||||
range: range_from_object(vm, source_file, object, "TypeVar")?,
|
||||
})
|
||||
let range = type_param_range_from_object(vm, source_file, object.clone())?;
|
||||
type_var_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
fn param_spec_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<ast::TypeParamParamSpec> {
|
||||
Ok(ast::TypeParamParamSpec {
|
||||
node_index: Default::default(),
|
||||
name: get_required_identifier_field(vm, source_file, &object, "name", "ParamSpec")?,
|
||||
default: get_node_field_opt(vm, &object, "default_value")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
range,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for ast::TypeParamParamSpec {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
@@ -145,24 +213,28 @@ impl Node for ast::TypeParamParamSpec {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
name: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "name", "ParamSpec")?,
|
||||
)?,
|
||||
default: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "default_value", "ParamSpec")?,
|
||||
)?,
|
||||
range: range_from_object(vm, source_file, object, "ParamSpec")?,
|
||||
})
|
||||
let range = type_param_range_from_object(vm, source_file, object.clone())?;
|
||||
param_spec_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
fn type_var_tuple_from_object_with_range(
|
||||
vm: &VirtualMachine,
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
range: TextRange,
|
||||
) -> PyResult<ast::TypeParamTypeVarTuple> {
|
||||
Ok(ast::TypeParamTypeVarTuple {
|
||||
node_index: Default::default(),
|
||||
name: get_required_identifier_field(vm, source_file, &object, "name", "TypeVarTuple")?,
|
||||
default: get_node_field_opt(vm, &object, "default_value")?
|
||||
.map(|obj| Node::ast_from_object(vm, source_file, obj))
|
||||
.transpose()?,
|
||||
range,
|
||||
})
|
||||
}
|
||||
|
||||
impl Node for ast::TypeParamTypeVarTuple {
|
||||
fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef {
|
||||
let Self {
|
||||
@@ -191,19 +263,7 @@ impl Node for ast::TypeParamTypeVarTuple {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
node_index: Default::default(),
|
||||
name: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "name", "TypeVarTuple")?,
|
||||
)?,
|
||||
default: Node::ast_from_object(
|
||||
vm,
|
||||
source_file,
|
||||
get_node_field(vm, &object, "default_value", "TypeVarTuple")?,
|
||||
)?,
|
||||
range: range_from_object(vm, source_file, object, "TypeVarTuple")?,
|
||||
})
|
||||
let range = type_param_range_from_object(vm, source_file, object.clone())?;
|
||||
type_var_tuple_from_object_with_range(vm, source_file, object, range)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
// spell-checker: ignore assignlist ifexp
|
||||
|
||||
use super::module::Mod;
|
||||
use crate::{PyResult, VirtualMachine};
|
||||
use crate::{PyResult, VirtualMachine, compiler::CompileError};
|
||||
use ruff_python_ast as ast;
|
||||
use rustpython_codegen::error::{CodegenError, CodegenErrorType};
|
||||
use rustpython_compiler_core::bytecode::ConstantData;
|
||||
|
||||
fn expr_context_name(ctx: ast::ExprContext) -> &'static str {
|
||||
match ctx {
|
||||
@@ -13,6 +15,17 @@ fn expr_context_name(ctx: ast::ExprContext) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid_syntax_error(vm: &VirtualMachine) -> crate::builtins::PyBaseExceptionRef {
|
||||
vm.new_syntax_error(
|
||||
&CompileError::Codegen(CodegenError {
|
||||
location: None,
|
||||
error: CodegenErrorType::SyntaxError("invalid syntax".to_owned()),
|
||||
source_path: "<unknown>".to_owned(),
|
||||
}),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn validate_name(vm: &VirtualMachine, name: &ast::name::Name) -> PyResult<()> {
|
||||
match name.as_str() {
|
||||
"None" | "True" | "False" => Err(vm.new_value_error(format!(
|
||||
@@ -30,6 +43,7 @@ fn validate_comprehension(vm: &VirtualMachine, gens: &[ast::Comprehension]) -> P
|
||||
for comp in gens {
|
||||
validate_expr(vm, &comp.target, ast::ExprContext::Store)?;
|
||||
validate_expr(vm, &comp.iter, ast::ExprContext::Load)?;
|
||||
validate_runtime_expr_list_slots(vm, comp.runtime_ifs.as_ref(), ast::ExprContext::Load)?;
|
||||
validate_exprs(vm, &comp.ifs, ast::ExprContext::Load, false)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -42,30 +56,49 @@ fn validate_keywords(vm: &VirtualMachine, keywords: &[ast::Keyword]) -> PyResult
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_parameter_annotation(vm: &VirtualMachine, parameter: &ast::Parameter) -> PyResult<()> {
|
||||
if let Some(annotation) = ¶meter.annotation {
|
||||
validate_expr(vm, annotation, ast::ExprContext::Load)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_parameters(vm: &VirtualMachine, params: &ast::Parameters) -> PyResult<()> {
|
||||
for param in params
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(¶ms.args)
|
||||
.chain(¶ms.kwonlyargs)
|
||||
{
|
||||
if let Some(annotation) = ¶m.parameter.annotation {
|
||||
validate_expr(vm, annotation, ast::ExprContext::Load)?;
|
||||
}
|
||||
if let Some(default) = ¶m.default {
|
||||
validate_expr(vm, default, ast::ExprContext::Load)?;
|
||||
}
|
||||
for param in params.posonlyargs.iter().chain(¶ms.args) {
|
||||
validate_parameter_annotation(vm, ¶m.parameter)?;
|
||||
}
|
||||
if let Some(vararg) = ¶ms.vararg
|
||||
&& let Some(annotation) = &vararg.annotation
|
||||
{
|
||||
validate_expr(vm, annotation, ast::ExprContext::Load)?;
|
||||
}
|
||||
for param in ¶ms.kwonlyargs {
|
||||
validate_parameter_annotation(vm, ¶m.parameter)?;
|
||||
}
|
||||
if let Some(kwarg) = ¶ms.kwarg
|
||||
&& let Some(annotation) = &kwarg.annotation
|
||||
{
|
||||
validate_expr(vm, annotation, ast::ExprContext::Load)?;
|
||||
}
|
||||
if let Some(defaults) = params.runtime_defaults.as_ref() {
|
||||
for default in defaults {
|
||||
let Some(default) = default else {
|
||||
return Err(vm.new_value_error("None disallowed in expression list"));
|
||||
};
|
||||
validate_expr(vm, default, ast::ExprContext::Load)?;
|
||||
}
|
||||
} else {
|
||||
for param in params.posonlyargs.iter().chain(¶ms.args) {
|
||||
if let Some(default) = ¶m.default {
|
||||
validate_expr(vm, default, ast::ExprContext::Load)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
for param in ¶ms.kwonlyargs {
|
||||
if let Some(default) = ¶m.default {
|
||||
validate_expr(vm, default, ast::ExprContext::Load)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -99,8 +132,14 @@ fn validate_assignlist(
|
||||
validate_exprs(vm, targets, ctx, false)
|
||||
}
|
||||
|
||||
fn validate_body(vm: &VirtualMachine, body: &[ast::Stmt], owner: &'static str) -> PyResult<()> {
|
||||
fn validate_body(
|
||||
vm: &VirtualMachine,
|
||||
body: &[ast::Stmt],
|
||||
metadata: Option<&Vec<Option<ast::Stmt>>>,
|
||||
owner: &'static str,
|
||||
) -> PyResult<()> {
|
||||
validate_nonempty_seq(vm, body.len(), "body", owner)?;
|
||||
validate_runtime_stmt_list_slots(vm, metadata)?;
|
||||
validate_stmts(vm, body)
|
||||
}
|
||||
|
||||
@@ -111,34 +150,83 @@ fn validate_interpolated_elements<'a>(
|
||||
for element in elements {
|
||||
if let ast::InterpolatedStringElementRef::Interpolation(interpolation) = element {
|
||||
validate_expr(vm, &interpolation.expression, ast::ExprContext::Load)?;
|
||||
if let Some(format_spec) = &interpolation.format_spec {
|
||||
for spec_element in &format_spec.elements {
|
||||
if let ast::InterpolatedStringElement::Interpolation(spec_interp) = spec_element
|
||||
{
|
||||
validate_expr(vm, &spec_interp.expression, ast::ExprContext::Load)?;
|
||||
}
|
||||
}
|
||||
if let Some(format_spec) = interpolation.runtime_formatted_value_format_spec.as_deref()
|
||||
{
|
||||
validate_expr(vm, format_spec, ast::ExprContext::Load)?;
|
||||
} else if let Some(format_spec) =
|
||||
interpolation.runtime_interpolation_format_spec.as_deref()
|
||||
{
|
||||
validate_expr(vm, format_spec, ast::ExprContext::Load)?;
|
||||
} else if let Some(format_spec) = &interpolation.format_spec {
|
||||
validate_interpolated_elements(
|
||||
vm,
|
||||
format_spec
|
||||
.elements
|
||||
.iter()
|
||||
.map(ast::InterpolatedStringElementRef::from),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_literal_number(expr: &ast::Expr, allow_real: bool, allow_imaginary: bool) -> bool {
|
||||
let ast::Expr::NumberLiteral(number) = expr else {
|
||||
return false;
|
||||
};
|
||||
match number.value {
|
||||
ast::Number::Int(_) | ast::Number::Float(_) => allow_real,
|
||||
ast::Number::Complex { .. } => allow_imaginary,
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_literal_negative(expr: &ast::Expr, allow_real: bool, allow_imaginary: bool) -> bool {
|
||||
let ast::Expr::UnaryOp(unary) = expr else {
|
||||
return false;
|
||||
};
|
||||
if unary.op != ast::UnaryOp::USub {
|
||||
return false;
|
||||
}
|
||||
ensure_literal_number(&unary.operand, allow_real, allow_imaginary)
|
||||
}
|
||||
|
||||
fn ensure_literal_complex(expr: &ast::Expr) -> bool {
|
||||
let ast::Expr::BinOp(bin) = expr else {
|
||||
return false;
|
||||
};
|
||||
if !matches!(bin.op, ast::Operator::Add | ast::Operator::Sub) {
|
||||
return false;
|
||||
}
|
||||
let real_left = ensure_literal_number(&bin.left, true, false)
|
||||
|| ensure_literal_negative(&bin.left, true, false);
|
||||
real_left && ensure_literal_number(&bin.right, false, true)
|
||||
}
|
||||
|
||||
fn ast_constant_value(expr: &ast::Expr) -> Option<ConstantData> {
|
||||
expr.as_constant_expr()
|
||||
.map(|expr| super::constant::ast_constant_value_to_constant_data(expr.value.clone()))
|
||||
}
|
||||
|
||||
fn validate_pattern_match_value(vm: &VirtualMachine, expr: &ast::Expr) -> PyResult<()> {
|
||||
validate_expr(vm, expr, ast::ExprContext::Load)?;
|
||||
if let Some(constant) = ast_constant_value(expr) {
|
||||
return match &constant {
|
||||
ConstantData::Integer { .. }
|
||||
| ConstantData::Float { .. }
|
||||
| ConstantData::Bytes { .. }
|
||||
| ConstantData::Complex { .. }
|
||||
| ConstantData::Str { .. } => Ok(()),
|
||||
_ => Err(vm.new_value_error("unexpected constant inside of a literal pattern")),
|
||||
};
|
||||
}
|
||||
match expr {
|
||||
ast::Expr::NumberLiteral(_) | ast::Expr::StringLiteral(_) | ast::Expr::BytesLiteral(_) => {
|
||||
Ok(())
|
||||
}
|
||||
ast::Expr::Attribute(_) => Ok(()),
|
||||
ast::Expr::UnaryOp(op) => match &*op.operand {
|
||||
ast::Expr::NumberLiteral(_) => Ok(()),
|
||||
_ => Err(vm.new_value_error("patterns may only match literals and attribute lookups")),
|
||||
},
|
||||
ast::Expr::BinOp(bin) => match (&*bin.left, &*bin.right) {
|
||||
(ast::Expr::NumberLiteral(_), ast::Expr::NumberLiteral(_)) => Ok(()),
|
||||
_ => Err(vm.new_value_error("patterns may only match literals and attribute lookups")),
|
||||
},
|
||||
ast::Expr::UnaryOp(_) if ensure_literal_negative(expr, true, true) => Ok(()),
|
||||
ast::Expr::BinOp(_) if ensure_literal_complex(expr) => Ok(()),
|
||||
ast::Expr::FString(_) | ast::Expr::TString(_) => Ok(()),
|
||||
ast::Expr::BooleanLiteral(_)
|
||||
| ast::Expr::NoneLiteral(_)
|
||||
@@ -162,7 +250,10 @@ fn validate_pattern(vm: &VirtualMachine, pattern: &ast::Pattern, star_ok: bool)
|
||||
ast::Pattern::MatchSingleton(singleton) => match singleton.value {
|
||||
ast::Singleton::None | ast::Singleton::True | ast::Singleton::False => Ok(()),
|
||||
},
|
||||
ast::Pattern::MatchSequence(seq) => validate_patterns(vm, &seq.patterns, true),
|
||||
ast::Pattern::MatchSequence(seq) => {
|
||||
validate_runtime_pattern_list_slots(vm, seq.runtime_patterns.as_ref())?;
|
||||
validate_patterns(vm, &seq.patterns, true)
|
||||
}
|
||||
ast::Pattern::MatchMapping(mapping) => {
|
||||
if mapping.keys.len() != mapping.patterns.len() {
|
||||
return Err(vm.new_value_error(
|
||||
@@ -172,15 +263,34 @@ fn validate_pattern(vm: &VirtualMachine, pattern: &ast::Pattern, star_ok: bool)
|
||||
if let Some(rest) = &mapping.rest {
|
||||
validate_capture(vm, rest)?;
|
||||
}
|
||||
validate_runtime_expr_option_list_slots(vm, mapping.runtime_keys.as_ref())?;
|
||||
for key in &mapping.keys {
|
||||
if let ast::Expr::BooleanLiteral(_) | ast::Expr::NoneLiteral(_) = key {
|
||||
if matches!(
|
||||
key,
|
||||
ast::Expr::BooleanLiteral(_)
|
||||
| ast::Expr::NoneLiteral(_)
|
||||
| ast::Expr::Constant(ast::ExprConstant {
|
||||
value: ast::ConstantValue::Boolean(_) | ast::ConstantValue::None,
|
||||
..
|
||||
})
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
validate_pattern_match_value(vm, key)?;
|
||||
}
|
||||
validate_runtime_pattern_list_slots(vm, mapping.runtime_patterns.as_ref())?;
|
||||
validate_patterns(vm, &mapping.patterns, false)
|
||||
}
|
||||
ast::Pattern::MatchClass(match_class) => {
|
||||
if let (Some(kwd_attrs), Some(kwd_patterns)) = (
|
||||
match_class.runtime_kwd_attrs.as_ref(),
|
||||
match_class.runtime_kwd_patterns.as_ref(),
|
||||
) && kwd_attrs.len() != kwd_patterns.len()
|
||||
{
|
||||
return Err(vm.new_value_error(
|
||||
"MatchClass doesn't have the same number of keyword attributes as patterns",
|
||||
));
|
||||
}
|
||||
validate_expr(vm, &match_class.cls, ast::ExprContext::Load)?;
|
||||
let mut cls = match_class.cls.as_ref();
|
||||
loop {
|
||||
@@ -199,7 +309,13 @@ fn validate_pattern(vm: &VirtualMachine, pattern: &ast::Pattern, star_ok: bool)
|
||||
for keyword in &match_class.arguments.keywords {
|
||||
validate_name(vm, keyword.attr.id())?;
|
||||
}
|
||||
if let Some(patterns) = &match_class.runtime_patterns {
|
||||
validate_runtime_nullable_patterns(vm, patterns)?;
|
||||
}
|
||||
validate_patterns(vm, &match_class.arguments.patterns, false)?;
|
||||
if let Some(kwd_patterns) = &match_class.runtime_kwd_patterns {
|
||||
validate_runtime_nullable_patterns(vm, kwd_patterns)?;
|
||||
}
|
||||
for keyword in &match_class.arguments.keywords {
|
||||
validate_pattern(vm, &keyword.pattern, false)?;
|
||||
}
|
||||
@@ -234,11 +350,80 @@ fn validate_pattern(vm: &VirtualMachine, pattern: &ast::Pattern, star_ok: bool)
|
||||
if match_or.patterns.len() < 2 {
|
||||
return Err(vm.new_value_error("MatchOr requires at least 2 patterns"));
|
||||
}
|
||||
validate_runtime_pattern_list_slots(vm, match_or.runtime_patterns.as_ref())?;
|
||||
validate_patterns(vm, &match_or.patterns, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_runtime_pattern_list_slots(
|
||||
vm: &VirtualMachine,
|
||||
values: Option<&Vec<Option<ast::Pattern>>>,
|
||||
) -> PyResult<()> {
|
||||
if values.is_some_and(|values| values.iter().any(Option::is_none)) {
|
||||
return Err(vm.new_value_error("unexpected pattern"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_runtime_expr_option_list_slots(
|
||||
vm: &VirtualMachine,
|
||||
values: Option<&Vec<Option<ast::Expr>>>,
|
||||
) -> PyResult<()> {
|
||||
if values.is_some_and(|values| values.iter().any(Option::is_none)) {
|
||||
return Err(vm.new_value_error("None disallowed in expression list"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_runtime_expr_list_slots(
|
||||
vm: &VirtualMachine,
|
||||
values: Option<&Vec<Option<ast::Expr>>>,
|
||||
ctx: ast::ExprContext,
|
||||
) -> PyResult<()> {
|
||||
if let Some(values) = values {
|
||||
for value in values {
|
||||
let Some(value) = value else {
|
||||
return Err(vm.new_value_error("None disallowed in expression list"));
|
||||
};
|
||||
validate_expr(vm, value, ctx)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_runtime_stmt_list_slots(
|
||||
vm: &VirtualMachine,
|
||||
values: Option<&Vec<Option<ast::Stmt>>>,
|
||||
) -> PyResult<()> {
|
||||
if let Some(values) = values
|
||||
&& values.iter().any(Option::is_none)
|
||||
{
|
||||
return Err(vm.new_value_error("None disallowed in statement list"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_runtime_except_handler_list_slots(
|
||||
vm: &VirtualMachine,
|
||||
values: Option<&Vec<Option<ast::ExceptHandler>>>,
|
||||
) -> PyResult<()> {
|
||||
if values.is_some_and(|values| values.iter().any(Option::is_none)) {
|
||||
return Err(vm.new_value_error("unexpected excepthandler"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_runtime_nullable_patterns(
|
||||
vm: &VirtualMachine,
|
||||
patterns: &[Option<ast::Pattern>],
|
||||
) -> PyResult<()> {
|
||||
if patterns.iter().any(Option::is_none) {
|
||||
return Err(vm.new_value_error("unexpected pattern"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_patterns(
|
||||
vm: &VirtualMachine,
|
||||
patterns: &[ast::Pattern],
|
||||
@@ -282,6 +467,12 @@ fn validate_type_params(
|
||||
type_params: Option<&ast::TypeParams>,
|
||||
) -> PyResult<()> {
|
||||
if let Some(type_params) = type_params {
|
||||
if let Some(values) = type_params.runtime_type_params.as_ref() {
|
||||
for tp in values.iter().flatten() {
|
||||
validate_typeparam(vm, tp)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
for tp in &type_params.type_params {
|
||||
validate_typeparam(vm, tp)?;
|
||||
}
|
||||
@@ -337,6 +528,11 @@ fn validate_expr(vm: &VirtualMachine, expr: &ast::Expr, ctx: ast::ExprContext) -
|
||||
if op.values.len() < 2 {
|
||||
return Err(vm.new_value_error("BoolOp with less than 2 values"));
|
||||
}
|
||||
validate_runtime_expr_list_slots(
|
||||
vm,
|
||||
op.runtime_values.as_ref(),
|
||||
ast::ExprContext::Load,
|
||||
)?;
|
||||
validate_exprs(vm, &op.values, ast::ExprContext::Load, false)
|
||||
}
|
||||
ast::Expr::Named(named) => {
|
||||
@@ -362,6 +558,11 @@ fn validate_expr(vm: &VirtualMachine, expr: &ast::Expr, ctx: ast::ExprContext) -
|
||||
validate_expr(vm, &ifexp.orelse, ast::ExprContext::Load)
|
||||
}
|
||||
ast::Expr::Dict(dict) => {
|
||||
validate_runtime_expr_list_slots(
|
||||
vm,
|
||||
dict.runtime_values.as_ref(),
|
||||
ast::ExprContext::Load,
|
||||
)?;
|
||||
for item in &dict.items {
|
||||
if let Some(key) = &item.key {
|
||||
validate_expr(vm, key, ast::ExprContext::Load)?;
|
||||
@@ -370,7 +571,14 @@ fn validate_expr(vm: &VirtualMachine, expr: &ast::Expr, ctx: ast::ExprContext) -
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ast::Expr::Set(set) => validate_exprs(vm, &set.elts, ast::ExprContext::Load, false),
|
||||
ast::Expr::Set(set) => {
|
||||
validate_runtime_expr_list_slots(
|
||||
vm,
|
||||
set.runtime_elts.as_ref(),
|
||||
ast::ExprContext::Load,
|
||||
)?;
|
||||
validate_exprs(vm, &set.elts, ast::ExprContext::Load, false)
|
||||
}
|
||||
ast::Expr::ListComp(list) => {
|
||||
validate_comprehension(vm, &list.generators)?;
|
||||
validate_expr(vm, &list.elt, ast::ExprContext::Load)
|
||||
@@ -409,34 +617,73 @@ fn validate_expr(vm: &VirtualMachine, expr: &ast::Expr, ctx: ast::ExprContext) -
|
||||
"Compare has a different number of comparators and operands",
|
||||
));
|
||||
}
|
||||
validate_runtime_expr_list_slots(
|
||||
vm,
|
||||
compare.runtime_comparators.as_ref(),
|
||||
ast::ExprContext::Load,
|
||||
)?;
|
||||
validate_exprs(vm, &compare.comparators, ast::ExprContext::Load, false)?;
|
||||
validate_expr(vm, &compare.left, ast::ExprContext::Load)
|
||||
}
|
||||
ast::Expr::Call(call) => {
|
||||
validate_expr(vm, &call.func, ast::ExprContext::Load)?;
|
||||
validate_runtime_expr_list_slots(
|
||||
vm,
|
||||
call.arguments.runtime_args.as_ref(),
|
||||
ast::ExprContext::Load,
|
||||
)?;
|
||||
validate_exprs(vm, &call.arguments.args, ast::ExprContext::Load, false)?;
|
||||
validate_keywords(vm, &call.arguments.keywords)
|
||||
}
|
||||
ast::Expr::FString(fstring) => validate_interpolated_elements(
|
||||
vm,
|
||||
fstring
|
||||
.value
|
||||
.elements()
|
||||
.map(ast::InterpolatedStringElementRef::from),
|
||||
),
|
||||
ast::Expr::TString(tstring) => validate_interpolated_elements(
|
||||
vm,
|
||||
tstring
|
||||
.value
|
||||
.elements()
|
||||
.map(ast::InterpolatedStringElementRef::from),
|
||||
),
|
||||
ast::Expr::FString(fstring) => {
|
||||
validate_runtime_expr_list_slots(
|
||||
vm,
|
||||
fstring.runtime_values.as_ref(),
|
||||
ast::ExprContext::Load,
|
||||
)?;
|
||||
if let Some(joined_str) = fstring.runtime_joined_str.as_ref() {
|
||||
validate_exprs(vm, joined_str, ast::ExprContext::Load, false)
|
||||
} else {
|
||||
validate_interpolated_elements(
|
||||
vm,
|
||||
fstring
|
||||
.value
|
||||
.elements()
|
||||
.map(ast::InterpolatedStringElementRef::from),
|
||||
)
|
||||
}
|
||||
}
|
||||
ast::Expr::TString(tstring) => {
|
||||
validate_runtime_expr_list_slots(
|
||||
vm,
|
||||
tstring.runtime_values.as_ref(),
|
||||
ast::ExprContext::Load,
|
||||
)?;
|
||||
if let Some(template_str) = tstring.runtime_template_str.as_ref() {
|
||||
validate_exprs(vm, template_str, ast::ExprContext::Load, false)
|
||||
} else {
|
||||
validate_interpolated_elements(
|
||||
vm,
|
||||
tstring
|
||||
.value
|
||||
.elements()
|
||||
.map(ast::InterpolatedStringElementRef::from),
|
||||
)
|
||||
}
|
||||
}
|
||||
ast::Expr::StringLiteral(_)
|
||||
| ast::Expr::BytesLiteral(_)
|
||||
| ast::Expr::NumberLiteral(_)
|
||||
| ast::Expr::Constant(_)
|
||||
| ast::Expr::BooleanLiteral(_)
|
||||
| ast::Expr::NoneLiteral(_)
|
||||
| ast::Expr::EllipsisLiteral(_) => Ok(()),
|
||||
| ast::Expr::EllipsisLiteral(_) => {
|
||||
if let Some(invalid_type) = super::constant::invalid_constant_type(expr) {
|
||||
Err(vm.new_type_error(format!("got an invalid type in Constant: {invalid_type}")))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
ast::Expr::Attribute(attr) => validate_expr(vm, &attr.value, ast::ExprContext::Load),
|
||||
ast::Expr::Subscript(sub) => {
|
||||
validate_expr(vm, &sub.slice, ast::ExprContext::Load)?;
|
||||
@@ -444,8 +691,14 @@ fn validate_expr(vm: &VirtualMachine, expr: &ast::Expr, ctx: ast::ExprContext) -
|
||||
}
|
||||
ast::Expr::Starred(star) => validate_expr(vm, &star.value, ctx),
|
||||
ast::Expr::Name(_) => Ok(()),
|
||||
ast::Expr::List(list) => validate_exprs(vm, &list.elts, ctx, false),
|
||||
ast::Expr::Tuple(tuple) => validate_exprs(vm, &tuple.elts, ctx, false),
|
||||
ast::Expr::List(list) => {
|
||||
validate_runtime_expr_list_slots(vm, list.runtime_elts.as_ref(), ctx)?;
|
||||
validate_exprs(vm, &list.elts, ctx, false)
|
||||
}
|
||||
ast::Expr::Tuple(tuple) => {
|
||||
validate_runtime_expr_list_slots(vm, tuple.runtime_elts.as_ref(), ctx)?;
|
||||
validate_exprs(vm, &tuple.elts, ctx, false)
|
||||
}
|
||||
ast::Expr::Slice(slice) => {
|
||||
if let Some(lower) = &slice.lower {
|
||||
validate_expr(vm, lower, ast::ExprContext::Load)?;
|
||||
@@ -458,7 +711,7 @@ fn validate_expr(vm: &VirtualMachine, expr: &ast::Expr, ctx: ast::ExprContext) -
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ast::Expr::IpyEscapeCommand(_) => Ok(()),
|
||||
ast::Expr::IpyEscapeCommand(_) => Err(invalid_syntax_error(vm)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,9 +730,14 @@ fn validate_stmt(vm: &VirtualMachine, stmt: &ast::Stmt) -> PyResult<()> {
|
||||
} else {
|
||||
"FunctionDef"
|
||||
};
|
||||
validate_body(vm, &func.body, owner)?;
|
||||
validate_body(vm, &func.body, func.runtime_body.as_ref(), owner)?;
|
||||
validate_type_params(vm, func.type_params.as_deref())?;
|
||||
validate_parameters(vm, &func.parameters)?;
|
||||
validate_runtime_expr_list_slots(
|
||||
vm,
|
||||
func.runtime_decorator_list.as_ref(),
|
||||
ast::ExprContext::Load,
|
||||
)?;
|
||||
validate_decorators(vm, &func.decorator_list)?;
|
||||
if let Some(returns) = &func.returns {
|
||||
validate_expr(vm, returns, ast::ExprContext::Load)?;
|
||||
@@ -487,12 +745,27 @@ fn validate_stmt(vm: &VirtualMachine, stmt: &ast::Stmt) -> PyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
ast::Stmt::ClassDef(class_def) => {
|
||||
validate_body(vm, &class_def.body, "ClassDef")?;
|
||||
validate_body(
|
||||
vm,
|
||||
&class_def.body,
|
||||
class_def.runtime_body.as_ref(),
|
||||
"ClassDef",
|
||||
)?;
|
||||
validate_type_params(vm, class_def.type_params.as_deref())?;
|
||||
if let Some(arguments) = &class_def.arguments {
|
||||
validate_runtime_expr_list_slots(
|
||||
vm,
|
||||
arguments.runtime_bases.as_ref(),
|
||||
ast::ExprContext::Load,
|
||||
)?;
|
||||
validate_exprs(vm, &arguments.args, ast::ExprContext::Load, false)?;
|
||||
validate_keywords(vm, &arguments.keywords)?;
|
||||
}
|
||||
validate_runtime_expr_list_slots(
|
||||
vm,
|
||||
class_def.runtime_decorator_list.as_ref(),
|
||||
ast::ExprContext::Load,
|
||||
)?;
|
||||
validate_decorators(vm, &class_def.decorator_list)
|
||||
}
|
||||
ast::Stmt::Return(ret) => {
|
||||
@@ -501,8 +774,20 @@ fn validate_stmt(vm: &VirtualMachine, stmt: &ast::Stmt) -> PyResult<()> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ast::Stmt::Delete(del) => validate_assignlist(vm, &del.targets, ast::ExprContext::Del),
|
||||
ast::Stmt::Delete(del) => {
|
||||
validate_runtime_expr_list_slots(
|
||||
vm,
|
||||
del.runtime_targets.as_ref(),
|
||||
ast::ExprContext::Del,
|
||||
)?;
|
||||
validate_assignlist(vm, &del.targets, ast::ExprContext::Del)
|
||||
}
|
||||
ast::Stmt::Assign(assign) => {
|
||||
validate_runtime_expr_list_slots(
|
||||
vm,
|
||||
assign.runtime_targets.as_ref(),
|
||||
ast::ExprContext::Store,
|
||||
)?;
|
||||
validate_assignlist(vm, &assign.targets, ast::ExprContext::Store)?;
|
||||
validate_expr(vm, &assign.value, ast::ExprContext::Load)
|
||||
}
|
||||
@@ -532,22 +817,30 @@ fn validate_stmt(vm: &VirtualMachine, stmt: &ast::Stmt) -> PyResult<()> {
|
||||
let owner = if for_stmt.is_async { "AsyncFor" } else { "For" };
|
||||
validate_expr(vm, &for_stmt.target, ast::ExprContext::Store)?;
|
||||
validate_expr(vm, &for_stmt.iter, ast::ExprContext::Load)?;
|
||||
validate_body(vm, &for_stmt.body, owner)?;
|
||||
validate_body(vm, &for_stmt.body, for_stmt.runtime_body.as_ref(), owner)?;
|
||||
validate_runtime_stmt_list_slots(vm, for_stmt.runtime_orelse.as_ref())?;
|
||||
validate_stmts(vm, &for_stmt.orelse)
|
||||
}
|
||||
ast::Stmt::While(while_stmt) => {
|
||||
validate_expr(vm, &while_stmt.test, ast::ExprContext::Load)?;
|
||||
validate_body(vm, &while_stmt.body, "While")?;
|
||||
validate_body(
|
||||
vm,
|
||||
&while_stmt.body,
|
||||
while_stmt.runtime_body.as_ref(),
|
||||
"While",
|
||||
)?;
|
||||
validate_runtime_stmt_list_slots(vm, while_stmt.runtime_orelse.as_ref())?;
|
||||
validate_stmts(vm, &while_stmt.orelse)
|
||||
}
|
||||
ast::Stmt::If(if_stmt) => {
|
||||
validate_expr(vm, &if_stmt.test, ast::ExprContext::Load)?;
|
||||
validate_body(vm, &if_stmt.body, "If")?;
|
||||
validate_body(vm, &if_stmt.body, if_stmt.runtime_body.as_ref(), "If")?;
|
||||
for clause in &if_stmt.elif_else_clauses {
|
||||
if let Some(test) = &clause.test {
|
||||
validate_expr(vm, test, ast::ExprContext::Load)?;
|
||||
}
|
||||
validate_body(vm, &clause.body, "If")?;
|
||||
validate_body(vm, &clause.body, clause.runtime_body.as_ref(), "If")?;
|
||||
validate_runtime_stmt_list_slots(vm, clause.runtime_orelse.as_ref())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -564,7 +857,7 @@ fn validate_stmt(vm: &VirtualMachine, stmt: &ast::Stmt) -> PyResult<()> {
|
||||
validate_expr(vm, optional_vars, ast::ExprContext::Store)?;
|
||||
}
|
||||
}
|
||||
validate_body(vm, &with_stmt.body, owner)
|
||||
validate_body(vm, &with_stmt.body, with_stmt.runtime_body.as_ref(), owner)
|
||||
}
|
||||
ast::Stmt::Match(match_stmt) => {
|
||||
validate_expr(vm, &match_stmt.subject, ast::ExprContext::Load)?;
|
||||
@@ -574,7 +867,7 @@ fn validate_stmt(vm: &VirtualMachine, stmt: &ast::Stmt) -> PyResult<()> {
|
||||
if let Some(guard) = &case.guard {
|
||||
validate_expr(vm, guard, ast::ExprContext::Load)?;
|
||||
}
|
||||
validate_body(vm, &case.body, "match_case")?;
|
||||
validate_body(vm, &case.body, case.runtime_body.as_ref(), "match_case")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -591,7 +884,7 @@ fn validate_stmt(vm: &VirtualMachine, stmt: &ast::Stmt) -> PyResult<()> {
|
||||
}
|
||||
ast::Stmt::Try(try_stmt) => {
|
||||
let owner = if try_stmt.is_star { "TryStar" } else { "Try" };
|
||||
validate_body(vm, &try_stmt.body, owner)?;
|
||||
validate_body(vm, &try_stmt.body, try_stmt.runtime_body.as_ref(), owner)?;
|
||||
if try_stmt.handlers.is_empty() && try_stmt.finalbody.is_empty() {
|
||||
return Err(vm.new_value_error(format!(
|
||||
"{owner} has neither except handlers nor finalbody"
|
||||
@@ -602,14 +895,22 @@ fn validate_stmt(vm: &VirtualMachine, stmt: &ast::Stmt) -> PyResult<()> {
|
||||
vm.new_value_error(format!("{owner} has orelse but no except handlers"))
|
||||
);
|
||||
}
|
||||
validate_runtime_except_handler_list_slots(vm, try_stmt.runtime_handlers.as_ref())?;
|
||||
for handler in &try_stmt.handlers {
|
||||
let ast::ExceptHandler::ExceptHandler(handler) = handler;
|
||||
if let Some(type_expr) = &handler.type_ {
|
||||
validate_expr(vm, type_expr, ast::ExprContext::Load)?;
|
||||
}
|
||||
validate_body(vm, &handler.body, "ExceptHandler")?;
|
||||
validate_body(
|
||||
vm,
|
||||
&handler.body,
|
||||
handler.runtime_body.as_ref(),
|
||||
"ExceptHandler",
|
||||
)?;
|
||||
}
|
||||
validate_runtime_stmt_list_slots(vm, try_stmt.runtime_finalbody.as_ref())?;
|
||||
validate_stmts(vm, &try_stmt.finalbody)?;
|
||||
validate_runtime_stmt_list_slots(vm, try_stmt.runtime_orelse.as_ref())?;
|
||||
validate_stmts(vm, &try_stmt.orelse)
|
||||
}
|
||||
ast::Stmt::Assert(assert_stmt) => {
|
||||
@@ -624,6 +925,11 @@ fn validate_stmt(vm: &VirtualMachine, stmt: &ast::Stmt) -> PyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
ast::Stmt::ImportFrom(import) => {
|
||||
if let Some(level) = import.runtime_level
|
||||
&& level < 0
|
||||
{
|
||||
return Err(vm.new_value_error("Negative ImportFrom level"));
|
||||
}
|
||||
validate_nonempty_seq(vm, import.names.len(), "names", "ImportFrom")?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -636,10 +942,8 @@ fn validate_stmt(vm: &VirtualMachine, stmt: &ast::Stmt) -> PyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
ast::Stmt::Expr(expr) => validate_expr(vm, &expr.value, ast::ExprContext::Load),
|
||||
ast::Stmt::Pass(_)
|
||||
| ast::Stmt::Break(_)
|
||||
| ast::Stmt::Continue(_)
|
||||
| ast::Stmt::IpyEscapeCommand(_) => Ok(()),
|
||||
ast::Stmt::Pass(_) | ast::Stmt::Break(_) | ast::Stmt::Continue(_) => Ok(()),
|
||||
ast::Stmt::IpyEscapeCommand(_) => Err(invalid_syntax_error(vm)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -652,10 +956,17 @@ fn validate_stmts(vm: &VirtualMachine, stmts: &[ast::Stmt]) -> PyResult<()> {
|
||||
|
||||
pub(super) fn validate_mod(vm: &VirtualMachine, module: &Mod) -> PyResult<()> {
|
||||
match module {
|
||||
Mod::Module(module) => validate_stmts(vm, &module.body),
|
||||
Mod::Interactive(module) => validate_stmts(vm, &module.body),
|
||||
Mod::Module(module) => {
|
||||
validate_runtime_stmt_list_slots(vm, module.module.runtime_body.as_ref())?;
|
||||
validate_stmts(vm, &module.module.body)
|
||||
}
|
||||
Mod::Interactive(module) => {
|
||||
validate_runtime_stmt_list_slots(vm, module.runtime_body.as_ref())?;
|
||||
validate_stmts(vm, &module.body)
|
||||
}
|
||||
Mod::Expression(expr) => validate_expr(vm, &expr.body, ast::ExprContext::Load),
|
||||
Mod::FunctionType(func_type) => {
|
||||
validate_runtime_expr_option_list_slots(vm, func_type.runtime_argtypes.as_ref())?;
|
||||
validate_exprs(vm, &func_type.argtypes, ast::ExprContext::Load, false)?;
|
||||
validate_expr(vm, &func_type.returns, ast::ExprContext::Load)
|
||||
}
|
||||
|
||||
@@ -153,7 +153,9 @@ mod _symtable {
|
||||
CompilerScope::Class => TYPE_CLASS,
|
||||
CompilerScope::Module => TYPE_MODULE,
|
||||
CompilerScope::Annotation => TYPE_ANNOTATION,
|
||||
CompilerScope::TypeAlias => TYPE_TYPE_ALIAS,
|
||||
CompilerScope::TypeParams => TYPE_TYPE_PARAMETERS,
|
||||
CompilerScope::TypeVariable => TYPE_TYPE_VARIABLE,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+371
-340
@@ -13,11 +13,12 @@ mod builtins {
|
||||
PyByteArray, PyBytes, PyDictRef, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType,
|
||||
PyUtf8StrRef,
|
||||
enumerate::PyReverseSequenceIterator,
|
||||
function::{PyCellRef, PyFunction},
|
||||
function::{PyCell, PyCellRef, PyFunction},
|
||||
int::PyIntRef,
|
||||
iter::PyCallableIterator,
|
||||
list::{PyList, SortOptions},
|
||||
},
|
||||
bytecode,
|
||||
common::hash::PyHash,
|
||||
function::{
|
||||
ArgBytesLike, ArgCallable, ArgIndex, ArgIntoBool, ArgIterable, ArgMapping,
|
||||
@@ -29,9 +30,13 @@ mod builtins {
|
||||
readline::{Readline, ReadlineResult},
|
||||
stdlib::sys,
|
||||
types::PyComparisonOp,
|
||||
vm::compile_mode::{
|
||||
CompilerFlags, PY_EVAL_INPUT, PY_FILE_INPUT, PY_FUNC_TYPE_INPUT, PY_SINGLE_INPUT,
|
||||
compile_future_feature_mask, compile_future_features_from_flags,
|
||||
},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use num_traits::{Signed, ToPrimitive, Zero};
|
||||
use num_traits::{Signed, ToPrimitive};
|
||||
use rustpython_common::wtf8::CodePoint;
|
||||
|
||||
#[cfg(not(feature = "rustpython-compiler"))]
|
||||
@@ -103,8 +108,8 @@ mod builtins {
|
||||
filename: PyObjectRef,
|
||||
mode: PyUtf8StrRef,
|
||||
// CPython parity: flags / optimize accept any object with __index__,
|
||||
// not just exact int. Matches the behavior of `int(x)` arg conversion
|
||||
// used by Python/Python-ast.c::compile.
|
||||
// not just exact int. Matches the argument conversion used by
|
||||
// builtin_compile_impl.
|
||||
#[pyarg(any, optional)]
|
||||
flags: OptionalArg<ArgPrimitiveIndex<i32>>,
|
||||
// CPython parity: dont_inherit goes through PyObject_IsTrue, so
|
||||
@@ -114,174 +119,67 @@ mod builtins {
|
||||
dont_inherit: OptionalArg<ArgIntoBool>,
|
||||
#[pyarg(any, optional)]
|
||||
optimize: OptionalArg<ArgPrimitiveIndex<i32>>,
|
||||
#[pyarg(any, optional)]
|
||||
#[pyarg(named, optional)]
|
||||
_feature_version: OptionalArg<i32>,
|
||||
}
|
||||
|
||||
/// Detect PEP 263 encoding cookie from source bytes.
|
||||
/// Checks first two lines for `# coding[:=] <encoding>` pattern.
|
||||
/// Returns the encoding name if found, or None for default (UTF-8).
|
||||
#[cfg(feature = "parser")]
|
||||
fn detect_source_encoding(source: &[u8]) -> Option<String> {
|
||||
fn find_encoding_in_line(line: &[u8]) -> Option<String> {
|
||||
// PEP 263: '#' must be preceded only by whitespace/formfeed
|
||||
let hash_pos = line.iter().position(|&b| b == b'#')?;
|
||||
if !line[..hash_pos]
|
||||
.iter()
|
||||
.all(|&b| matches!(b, b' ' | b'\t' | b'\x0c' | b'\r'))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let after_hash = &line[hash_pos..];
|
||||
|
||||
// Find "coding" after the #
|
||||
let coding_pos = after_hash.windows(6).position(|w| w == b"coding")?;
|
||||
let after_coding = &after_hash[coding_pos + 6..];
|
||||
|
||||
// Next char must be ':' or '='
|
||||
let rest = if matches!(after_coding.first(), Some(b':' | b'=')) {
|
||||
&after_coding[1..]
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Skip whitespace
|
||||
let rest = rest
|
||||
.iter()
|
||||
.copied()
|
||||
.skip_while(|&b| matches!(b, b' ' | b'\t'))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Read encoding name: [-\w.]+
|
||||
let name = rest
|
||||
.iter()
|
||||
.take_while(|&&b| b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.'))
|
||||
.map(|&b| b as char)
|
||||
.collect::<String>();
|
||||
|
||||
if name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(normalize_source_encoding(&name))
|
||||
}
|
||||
fn merge_compile_future_features(
|
||||
flags: i32,
|
||||
dont_inherit: bool,
|
||||
vm: &VirtualMachine,
|
||||
) -> bytecode::CodeFlags {
|
||||
let mut future_features = compile_future_features_from_flags(flags);
|
||||
if !dont_inherit && let Some(frame) = vm.current_frame() {
|
||||
future_features |= bytecode::CodeFlags::from_bits_truncate(
|
||||
frame.code.flags.bits() & compile_future_feature_mask().bits(),
|
||||
);
|
||||
}
|
||||
|
||||
// Split into lines (first two only)
|
||||
let mut lines = source.splitn(3, |&b| b == b'\n');
|
||||
|
||||
if let Some(first) = lines.next() {
|
||||
// Strip BOM if present
|
||||
let first = first.strip_prefix(b"\xef\xbb\xbf").unwrap_or(first);
|
||||
if let Some(enc) = find_encoding_in_line(first) {
|
||||
return Some(enc);
|
||||
}
|
||||
// Only check second line if first line is blank or a comment
|
||||
let trimmed = first
|
||||
.iter()
|
||||
.find(|&&b| !matches!(b, b' ' | b'\t' | b'\x0c' | b'\r'))
|
||||
.copied();
|
||||
|
||||
if trimmed.is_some_and(|b| b != b'#') {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
lines.next().and_then(find_encoding_in_line)
|
||||
future_features
|
||||
}
|
||||
|
||||
/// Match CPython's Parser/tokenizer/helpers.c:get_normal_name().
|
||||
#[cfg(feature = "parser")]
|
||||
fn normalize_source_encoding(name: &str) -> String {
|
||||
let mut normalized = String::with_capacity(name.len().min(12));
|
||||
for ch in name.chars().take(12) {
|
||||
if ch == '_' {
|
||||
normalized.push('-');
|
||||
} else {
|
||||
normalized.push(ch.to_ascii_lowercase());
|
||||
}
|
||||
}
|
||||
fn audit_compile_source(vm: &VirtualMachine, source: &[u8], filename: &str) -> PyResult<()> {
|
||||
vm.sys_module.get_attr("audit", vm)?.call(
|
||||
(
|
||||
vm.ctx.new_str("compile"),
|
||||
vm.ctx.new_bytes(source.to_vec()),
|
||||
vm.ctx.new_str(filename),
|
||||
),
|
||||
vm,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
if normalized == "utf-8" || normalized.starts_with("utf-8-") {
|
||||
"utf-8".to_owned()
|
||||
} else if normalized == "latin-1"
|
||||
|| normalized == "iso-8859-1"
|
||||
|| normalized == "iso-latin-1"
|
||||
|| normalized.starts_with("latin-1-")
|
||||
|| normalized.starts_with("iso-8859-1-")
|
||||
|| normalized.starts_with("iso-latin-1-")
|
||||
fn trim_eval_source_bytes(mut source: &[u8]) -> &[u8] {
|
||||
while let Some((&first, rest)) = source.split_first()
|
||||
&& matches!(first, b' ' | b'\t')
|
||||
{
|
||||
"iso-8859-1".to_owned()
|
||||
} else {
|
||||
name.to_owned()
|
||||
source = rest;
|
||||
}
|
||||
source
|
||||
}
|
||||
|
||||
/// Decode source bytes to a string, handling PEP 263 encoding declarations
|
||||
/// and BOM. Raises SyntaxError for invalid UTF-8 without an encoding
|
||||
/// declaration.
|
||||
#[cfg(feature = "parser")]
|
||||
fn is_utf8_encoding(name: &str) -> bool {
|
||||
name == "utf-8"
|
||||
}
|
||||
|
||||
#[cfg(feature = "parser")]
|
||||
fn decode_source_bytes(source: &[u8], filename: &str, vm: &VirtualMachine) -> PyResult<String> {
|
||||
let has_bom = source.starts_with(b"\xef\xbb\xbf");
|
||||
let encoding = detect_source_encoding(source);
|
||||
|
||||
let is_utf8 = encoding.as_deref().is_none_or(is_utf8_encoding);
|
||||
|
||||
// Validate BOM + encoding combination
|
||||
if has_bom && !is_utf8 {
|
||||
let enc = encoding.as_deref().unwrap_or("utf-8");
|
||||
return Err(vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
format!("encoding problem: {enc} with BOM").into(),
|
||||
));
|
||||
fn decode_eval_exec_source_bytes(
|
||||
vm: &VirtualMachine,
|
||||
source: &[u8],
|
||||
filename: &str,
|
||||
) -> PyResult<String> {
|
||||
#[cfg(feature = "parser")]
|
||||
{
|
||||
vm.decode_source_bytes(source, filename, false)
|
||||
}
|
||||
|
||||
if is_utf8 {
|
||||
let src = if has_bom { &source[3..] } else { source };
|
||||
match core::str::from_utf8(src) {
|
||||
Ok(s) => Ok(s.to_owned()),
|
||||
Err(e) => {
|
||||
let bad_byte = src[e.valid_up_to()];
|
||||
let line = src[..e.valid_up_to()]
|
||||
.iter()
|
||||
.filter(|&&b| b == b'\n')
|
||||
.count()
|
||||
+ 1;
|
||||
Err(vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
format!(
|
||||
"Non-UTF-8 code starting with '\\x{bad_byte:02x}' \
|
||||
on line {line}, but no encoding declared; \
|
||||
see https://peps.python.org/pep-0263/ for details \
|
||||
({filename}, line {line})"
|
||||
)
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use codec registry for non-UTF-8 encodings
|
||||
let enc = encoding.as_deref().unwrap();
|
||||
let bytes_obj = vm.ctx.new_bytes(source.to_vec());
|
||||
let decoded = vm
|
||||
.state
|
||||
.codec_registry
|
||||
.decode_text(bytes_obj.into(), enc, None, vm)
|
||||
.map_err(|exc| {
|
||||
if exc.fast_isinstance(vm.ctx.exceptions.lookup_error) {
|
||||
vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
format!("unknown encoding for '{filename}': {enc}").into(),
|
||||
)
|
||||
} else {
|
||||
exc
|
||||
}
|
||||
})?;
|
||||
Ok(decoded.to_string_lossy().into_owned())
|
||||
#[cfg(not(feature = "parser"))]
|
||||
{
|
||||
_ = filename;
|
||||
core::str::from_utf8(source)
|
||||
.map(str::to_owned)
|
||||
.map_err(|err| {
|
||||
let msg = format!(
|
||||
"(unicode error) 'utf-8' codec can't decode byte 0x{:x?} in position {}: invalid start byte",
|
||||
source[err.valid_up_to()],
|
||||
err.valid_up_to()
|
||||
);
|
||||
vm.new_exception_msg(vm.ctx.exceptions.syntax_error.to_owned(), msg.into())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,30 +201,60 @@ mod builtins {
|
||||
|
||||
use crate::{class::PyClassImpl, stdlib::_ast};
|
||||
|
||||
let feature_version = feature_version_from_arg(args._feature_version, vm)?;
|
||||
let feature_version = args._feature_version.into_option().unwrap_or(-1);
|
||||
|
||||
let mode_str = args.mode.as_str();
|
||||
let flags: i32 = args.flags.map_or(0, |v| v.value);
|
||||
let cf = CompilerFlags::from_bits_retain(flags);
|
||||
|
||||
if (flags & !CompilerFlags::ALLOWED_FLAGS.bits()) != 0 {
|
||||
return Err(vm.new_value_error("compile(): unrecognised flags"));
|
||||
}
|
||||
|
||||
let optimize: i32 = args.optimize.map_or(-1, |v| v.value);
|
||||
let optimize: u8 = match optimize {
|
||||
-1 => vm.state.config.settings.optimize,
|
||||
-1 => vm.state.config.settings.optimize.min(2),
|
||||
0..=2 => optimize as u8,
|
||||
_ => return Err(vm.new_value_error("compile(): invalid optimize value")),
|
||||
};
|
||||
let dont_inherit = args.dont_inherit.map_or(false, ArgIntoBool::into_bool);
|
||||
let is_ast_only = cf.contains(CompilerFlags::ONLY_AST);
|
||||
let future_features = merge_compile_future_features(flags, dont_inherit, vm);
|
||||
|
||||
if args
|
||||
.source
|
||||
.fast_isinstance(&_ast::NodeAst::make_static_type())
|
||||
{
|
||||
let flags: i32 = args.flags.map_or(0, |v| v.value);
|
||||
let is_ast_only = !(flags & _ast::PY_CF_ONLY_AST).is_zero();
|
||||
|
||||
// func_type mode requires PyCF_ONLY_AST
|
||||
if mode_str == "func_type" && !is_ast_only {
|
||||
let start = if mode_str == "exec" {
|
||||
PY_FILE_INPUT
|
||||
} else if mode_str == "eval" {
|
||||
PY_EVAL_INPUT
|
||||
} else if mode_str == "single" {
|
||||
PY_SINGLE_INPUT
|
||||
} else if mode_str == "func_type" {
|
||||
if !is_ast_only {
|
||||
return Err(vm.new_value_error(
|
||||
"compile() mode 'func_type' requires flag PyCF_ONLY_AST",
|
||||
));
|
||||
}
|
||||
PY_FUNC_TYPE_INPUT
|
||||
} else {
|
||||
let msg = if is_ast_only {
|
||||
"compile() mode must be 'exec', 'eval', 'single' or 'func_type'"
|
||||
} else {
|
||||
"compile() mode must be 'exec', 'eval' or 'single'"
|
||||
};
|
||||
return Err(vm.new_value_error(msg));
|
||||
};
|
||||
|
||||
let ast_type = _ast::NodeAst::make_static_type().as_object().to_owned();
|
||||
if args.source.is_instance(&ast_type, vm)? {
|
||||
let explicit_future_annotations =
|
||||
future_features.contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
|
||||
vm.sys_module.get_attr("audit", vm)?.call(
|
||||
(
|
||||
vm.ctx.new_str("compile"),
|
||||
args.source.clone(),
|
||||
vm.ctx.none(),
|
||||
),
|
||||
vm,
|
||||
)?;
|
||||
|
||||
// compile(ast_node, ..., PyCF_ONLY_AST) returns the AST after validation
|
||||
if is_ast_only {
|
||||
@@ -336,15 +264,29 @@ mod builtins {
|
||||
"compile() mode must be 'exec', 'eval', 'single' or 'func_type'",
|
||||
)
|
||||
})?;
|
||||
if !args.source.fast_isinstance(&expected_type) {
|
||||
if !args.source.is_instance(expected_type.as_object(), vm)? {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected {} node, got {}",
|
||||
expected_name,
|
||||
args.source.class().name()
|
||||
)));
|
||||
}
|
||||
_ast::validate_ast_object(vm, args.source.clone())?;
|
||||
return Ok(args.source);
|
||||
#[cfg(not(feature = "rustpython-codegen"))]
|
||||
{
|
||||
_ast::validate_ast_object(vm, args.source.clone())?;
|
||||
return Ok(args.source);
|
||||
}
|
||||
#[cfg(feature = "rustpython-codegen")]
|
||||
{
|
||||
return _ast::preprocess_ast_object(
|
||||
vm,
|
||||
args.source,
|
||||
&filename.to_string_lossy(),
|
||||
optimize,
|
||||
cf.contains(CompilerFlags::OPTIMIZED_AST),
|
||||
explicit_future_annotations,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "rustpython-codegen"))]
|
||||
@@ -353,133 +295,104 @@ mod builtins {
|
||||
}
|
||||
#[cfg(feature = "rustpython-codegen")]
|
||||
{
|
||||
let (expected_type, expected_name) = _ast::mode_type_and_name(mode_str)
|
||||
.ok_or_else(|| {
|
||||
vm.new_value_error("compile() mode must be 'exec', 'eval' or 'single'")
|
||||
})?;
|
||||
if !args.source.is_instance(expected_type.as_object(), vm)? {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"expected {} node, got {}",
|
||||
expected_name,
|
||||
args.source.class().name()
|
||||
)));
|
||||
}
|
||||
let mode = mode_str
|
||||
.parse::<crate::compiler::Mode>()
|
||||
.map_err(|err| vm.new_value_error(err.to_string()))?;
|
||||
return _ast::compile(
|
||||
vm,
|
||||
args.source,
|
||||
&filename.to_string_lossy(),
|
||||
mode,
|
||||
Some(optimize),
|
||||
);
|
||||
let mut opts = vm.compile_opts();
|
||||
opts.optimize = optimize;
|
||||
opts.allow_top_level_await = cf.contains(CompilerFlags::ALLOW_TOP_LEVEL_AWAIT);
|
||||
opts.future_features = future_features;
|
||||
return _ast::compile(vm, args.source, &filename.to_string_lossy(), mode, opts);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "parser"))]
|
||||
return Err(vm.new_type_error(
|
||||
"can't compile() source code when the `parser` feature of rustpython is disabled",
|
||||
));
|
||||
|
||||
{
|
||||
Err(vm.new_type_error(
|
||||
"can't compile() source code when the `parser` feature of rustpython is disabled",
|
||||
))
|
||||
}
|
||||
#[cfg(feature = "parser")]
|
||||
{
|
||||
use crate::convert::ToPyException;
|
||||
|
||||
use ruff_python_parser as parser;
|
||||
|
||||
let source = ArgStrOrBytesLike::try_from_object(vm, args.source)?;
|
||||
let source = source.borrow_bytes();
|
||||
|
||||
let source = decode_source_bytes(&source, &filename.to_string_lossy(), vm)?;
|
||||
let source = source.as_str();
|
||||
|
||||
let flags: i32 = args.flags.map_or(0, |v| v.value);
|
||||
|
||||
if !(flags & !_ast::PY_COMPILE_FLAGS_MASK).is_zero() {
|
||||
return Err(vm.new_value_error("compile(): unrecognised flags"));
|
||||
}
|
||||
|
||||
let allow_incomplete = !(flags & _ast::PY_CF_ALLOW_INCOMPLETE_INPUT).is_zero();
|
||||
let type_comments = !(flags & _ast::PY_CF_TYPE_COMMENTS).is_zero();
|
||||
|
||||
let optimize_level = optimize;
|
||||
|
||||
if (flags & _ast::PY_CF_ONLY_AST).is_zero() {
|
||||
#[cfg(not(feature = "compiler"))]
|
||||
{
|
||||
Err(vm.new_value_error(CODEGEN_NOT_SUPPORTED))
|
||||
}
|
||||
#[cfg(feature = "compiler")]
|
||||
{
|
||||
if let Some(feature_version) = feature_version {
|
||||
let mode = mode_str
|
||||
.parse::<parser::Mode>()
|
||||
.map_err(|err| vm.new_value_error(err.to_string()))?;
|
||||
let _ = _ast::parse(
|
||||
vm,
|
||||
source,
|
||||
mode,
|
||||
optimize_level,
|
||||
Some(feature_version),
|
||||
type_comments,
|
||||
)
|
||||
.map_err(|e| (e, Some(source), allow_incomplete).to_pyexception(vm))?;
|
||||
}
|
||||
|
||||
let mode = mode_str
|
||||
.parse::<crate::compiler::Mode>()
|
||||
.map_err(|err| vm.new_value_error(err.to_string()))?;
|
||||
|
||||
let mut opts = vm.compile_opts();
|
||||
opts.optimize = optimize;
|
||||
|
||||
let code = vm
|
||||
.compile_with_opts(source, mode, &filename.to_string_lossy(), opts)
|
||||
.map_err(|err| {
|
||||
(err, Some(source), allow_incomplete).to_pyexception(vm)
|
||||
})?;
|
||||
Ok(code.into())
|
||||
}
|
||||
} else {
|
||||
if mode_str == "func_type" {
|
||||
return _ast::parse_func_type(vm, source, optimize_level, feature_version)
|
||||
.map_err(|e| (e, Some(source), allow_incomplete).to_pyexception(vm));
|
||||
}
|
||||
|
||||
let mode = mode_str
|
||||
.parse::<parser::Mode>()
|
||||
.map_err(|err| vm.new_value_error(err.to_string()))?;
|
||||
let parsed = _ast::parse(
|
||||
vm,
|
||||
let mut compile_flags = flags | future_features.bits() as i32;
|
||||
#[cfg(feature = "rustpython-compiler")]
|
||||
let compile_source = |source: &[u8], compile_flags: i32| {
|
||||
vm.compile_string_object_with_flags(
|
||||
source,
|
||||
mode,
|
||||
optimize_level,
|
||||
&filename.to_string_lossy(),
|
||||
start,
|
||||
compile_flags,
|
||||
feature_version,
|
||||
type_comments,
|
||||
optimize as i32,
|
||||
)
|
||||
.map_err(|e| (e, Some(source), allow_incomplete).to_pyexception(vm))?;
|
||||
|
||||
if mode_str == "single" {
|
||||
return _ast::wrap_interactive(vm, parsed);
|
||||
};
|
||||
match &source {
|
||||
ArgStrOrBytesLike::Str(source) => {
|
||||
if source.as_bytes().contains(&0) {
|
||||
return Err(vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
"source code string cannot contain null bytes".into(),
|
||||
));
|
||||
}
|
||||
audit_compile_source(
|
||||
vm,
|
||||
source.as_bytes(),
|
||||
filename.to_string_lossy().as_ref(),
|
||||
)?;
|
||||
compile_flags |= CompilerFlags::IGNORE_COOKIE.bits();
|
||||
#[cfg(feature = "rustpython-compiler")]
|
||||
{
|
||||
compile_source(source.as_bytes(), compile_flags)
|
||||
}
|
||||
#[cfg(not(feature = "rustpython-compiler"))]
|
||||
{
|
||||
Err(vm.new_value_error(CODEGEN_NOT_SUPPORTED))
|
||||
}
|
||||
}
|
||||
ArgStrOrBytesLike::Buf(source) => {
|
||||
let source_bytes = source.borrow_buf();
|
||||
let source_bytes: &[u8] = &source_bytes;
|
||||
if source_bytes.contains(&0) {
|
||||
return Err(vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
"source code string cannot contain null bytes".into(),
|
||||
));
|
||||
}
|
||||
audit_compile_source(
|
||||
vm,
|
||||
source_bytes,
|
||||
filename.to_string_lossy().as_ref(),
|
||||
)?;
|
||||
#[cfg(feature = "rustpython-compiler")]
|
||||
{
|
||||
compile_source(source_bytes, compile_flags)
|
||||
}
|
||||
#[cfg(not(feature = "rustpython-compiler"))]
|
||||
{
|
||||
Err(vm.new_value_error(CODEGEN_NOT_SUPPORTED))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ast")]
|
||||
fn feature_version_from_arg(
|
||||
feature_version: OptionalArg<i32>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Option<ruff_python_ast::PythonVersion>> {
|
||||
let Some(minor) = feature_version.into_option() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
if minor < 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
u8::try_from(minor)
|
||||
.map(|v| Some(ruff_python_ast::PythonVersion { major: 3, minor: v }))
|
||||
.map_err(|_| vm.new_value_error("compile() _feature_version out of range"))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn delattr(obj: PyObjectRef, attr: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let attr = attr.try_to_ref::<PyStr>(vm).map_err(|_| {
|
||||
let attr = attr.try_to_ref::<PyStr>(vm).map_err(|_e| {
|
||||
vm.new_type_error(format!(
|
||||
"attribute name must be string, not '{}'",
|
||||
attr.class().name()
|
||||
@@ -507,42 +420,39 @@ mod builtins {
|
||||
}
|
||||
|
||||
impl ScopeArgs {
|
||||
fn validate_globals_dict(
|
||||
globals: &PyObject,
|
||||
vm: &VirtualMachine,
|
||||
func_name: &'static str,
|
||||
) -> PyResult<()> {
|
||||
if globals.fast_isinstance(vm.ctx.types.dict_type) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let msg = match func_name {
|
||||
"eval" => {
|
||||
let is_mapping = globals.mapping_unchecked().check();
|
||||
if is_mapping {
|
||||
"globals must be a real dict; try eval(expr, {}, mapping)".into()
|
||||
} else {
|
||||
"globals must be a dict".into()
|
||||
}
|
||||
}
|
||||
"exec" => format!(
|
||||
"exec() globals must be a dict, not {}",
|
||||
globals.class().name()
|
||||
),
|
||||
_ => "globals must be a dict".into(),
|
||||
};
|
||||
|
||||
Err(vm.new_type_error(msg))
|
||||
}
|
||||
|
||||
fn make_scope(
|
||||
self,
|
||||
vm: &VirtualMachine,
|
||||
func_name: &'static str,
|
||||
) -> PyResult<crate::scope::Scope> {
|
||||
fn validate_globals_dict(
|
||||
globals: &PyObject,
|
||||
vm: &VirtualMachine,
|
||||
func_name: &'static str,
|
||||
) -> PyResult<()> {
|
||||
if !globals.fast_isinstance(vm.ctx.types.dict_type) {
|
||||
return Err(match func_name {
|
||||
"eval" => {
|
||||
let is_mapping = globals.mapping_unchecked().check();
|
||||
vm.new_type_error(if is_mapping {
|
||||
"globals must be a real dict; try eval(expr, {}, mapping)"
|
||||
} else {
|
||||
"globals must be a dict"
|
||||
})
|
||||
}
|
||||
"exec" => vm.new_type_error(format!(
|
||||
"exec() globals must be a dict, not {}",
|
||||
globals.class().name()
|
||||
)),
|
||||
_ => vm.new_type_error("globals must be a dict"),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let (globals, locals) = match self.globals {
|
||||
Some(globals) => {
|
||||
Self::validate_globals_dict(&globals, vm, func_name)?;
|
||||
validate_globals_dict(&globals, vm, func_name)?;
|
||||
|
||||
let globals = PyDictRef::try_from_object(vm, globals)?;
|
||||
if !globals.contains_key(identifier!(vm, __builtins__), vm) {
|
||||
@@ -570,6 +480,61 @@ mod builtins {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct ExecArgs {
|
||||
#[pyarg(positional)]
|
||||
source: Either<ArgStrOrBytesLike, PyRef<crate::builtins::PyCode>>,
|
||||
#[pyarg(any, default)]
|
||||
globals: Option<PyObjectRef>,
|
||||
#[pyarg(any, default)]
|
||||
locals: Option<ArgMapping>,
|
||||
#[pyarg(named, optional)]
|
||||
closure: OptionalOption<PyObjectRef>,
|
||||
}
|
||||
|
||||
fn exec_closure(
|
||||
code_obj: &PyRef<crate::builtins::PyCode>,
|
||||
closure: Option<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Option<PyRef<PyTuple<PyCellRef>>>> {
|
||||
let num_free = code_obj.freevars.len();
|
||||
let Some(closure) = closure else {
|
||||
if num_free == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
return Err(vm.new_type_error(format!(
|
||||
"code object requires a closure of exactly length {num_free}"
|
||||
)));
|
||||
};
|
||||
|
||||
if num_free == 0 {
|
||||
return Err(vm.new_type_error("cannot use a closure with this code object"));
|
||||
}
|
||||
|
||||
let closure_tuple = closure
|
||||
.downcast_exact::<PyTuple>(vm)
|
||||
.map_err(|_| {
|
||||
vm.new_type_error(format!(
|
||||
"code object requires a closure of exactly length {num_free}"
|
||||
))
|
||||
})?
|
||||
.into_pyref();
|
||||
if closure_tuple.len() != num_free {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"code object requires a closure of exactly length {num_free}"
|
||||
)));
|
||||
}
|
||||
|
||||
closure_tuple
|
||||
.try_into_typed::<PyCell>(vm)
|
||||
.map(Some)
|
||||
.map_err(|_| {
|
||||
vm.new_type_error(format!(
|
||||
"code object requires a closure of exactly length {num_free}"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn eval(
|
||||
source: Either<ArgStrOrBytesLike, PyRef<crate::builtins::PyCode>>,
|
||||
@@ -581,38 +546,93 @@ mod builtins {
|
||||
// source as string
|
||||
let code = match source {
|
||||
Either::A(either) => {
|
||||
let source: &[u8] = &either.borrow_bytes();
|
||||
if source.contains(&0) {
|
||||
return Err(vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
"source code string cannot contain null bytes".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let source = core::str::from_utf8(source).map_err(|err| {
|
||||
let msg = format!(
|
||||
"(unicode error) 'utf-8' codec can't decode byte 0x{:x?} in position {}: invalid start byte",
|
||||
source[err.valid_up_to()],
|
||||
err.valid_up_to()
|
||||
);
|
||||
|
||||
vm.new_exception_msg(vm.ctx.exceptions.syntax_error.to_owned(), msg.into())
|
||||
})?;
|
||||
Ok(Either::A(vm.ctx.new_utf8_str(source.trim_start())))
|
||||
let source = match &either {
|
||||
ArgStrOrBytesLike::Str(source) => {
|
||||
if source.as_bytes().contains(&0) {
|
||||
return Err(vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
"source code string cannot contain null bytes".into(),
|
||||
));
|
||||
}
|
||||
let source = source.expect_str().trim_start_matches([' ', '\t']);
|
||||
audit_compile_source(vm, source.as_bytes(), "<string>")?;
|
||||
source.to_owned()
|
||||
}
|
||||
ArgStrOrBytesLike::Buf(source) => {
|
||||
let source: &[u8] = &source.borrow_buf();
|
||||
if source.contains(&0) {
|
||||
return Err(vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
"source code string cannot contain null bytes".into(),
|
||||
));
|
||||
}
|
||||
let source = trim_eval_source_bytes(source);
|
||||
audit_compile_source(vm, source, "<string>")?;
|
||||
decode_eval_exec_source_bytes(vm, source, "eval")?
|
||||
}
|
||||
};
|
||||
Ok(Either::A(vm.ctx.new_utf8_str(source)))
|
||||
}
|
||||
Either::B(code) => Ok(Either::B(code)),
|
||||
}?;
|
||||
run_code(vm, code, scope, crate::compiler::Mode::Eval, "eval")
|
||||
run_code(vm, code, scope, crate::compiler::Mode::Eval, "eval", None)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn exec(
|
||||
source: Either<PyUtf8StrRef, PyRef<crate::builtins::PyCode>>,
|
||||
scope: ScopeArgs,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let scope = scope.make_scope(vm, "exec")?;
|
||||
run_code(vm, source, scope, crate::compiler::Mode::Exec, "exec")
|
||||
fn exec(args: ExecArgs, vm: &VirtualMachine) -> PyResult {
|
||||
let ExecArgs {
|
||||
source,
|
||||
globals,
|
||||
locals,
|
||||
closure,
|
||||
} = args;
|
||||
let scope = ScopeArgs { globals, locals }.make_scope(vm, "exec")?;
|
||||
let closure = closure.flatten();
|
||||
let (source, closure) = match source {
|
||||
Either::A(either) => {
|
||||
if closure.is_some() {
|
||||
return Err(
|
||||
vm.new_type_error("closure can only be used when source is a code object")
|
||||
);
|
||||
}
|
||||
let source = match &either {
|
||||
ArgStrOrBytesLike::Str(source) => {
|
||||
if source.as_bytes().contains(&0) {
|
||||
return Err(vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
"source code string cannot contain null bytes".into(),
|
||||
));
|
||||
}
|
||||
audit_compile_source(vm, source.as_bytes(), "<string>")?;
|
||||
source.expect_str().to_owned()
|
||||
}
|
||||
ArgStrOrBytesLike::Buf(source) => {
|
||||
let source: &[u8] = &source.borrow_buf();
|
||||
if source.contains(&0) {
|
||||
return Err(vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
"source code string cannot contain null bytes".into(),
|
||||
));
|
||||
}
|
||||
audit_compile_source(vm, source, "<string>")?;
|
||||
decode_eval_exec_source_bytes(vm, source, "exec")?
|
||||
}
|
||||
};
|
||||
(Either::A(vm.ctx.new_utf8_str(source)), None)
|
||||
}
|
||||
Either::B(code) => {
|
||||
let closure = exec_closure(&code, closure, vm)?;
|
||||
(Either::B(code), closure)
|
||||
}
|
||||
};
|
||||
run_code(
|
||||
vm,
|
||||
source,
|
||||
scope,
|
||||
crate::compiler::Mode::Exec,
|
||||
"exec",
|
||||
closure,
|
||||
)
|
||||
}
|
||||
|
||||
fn run_code(
|
||||
@@ -621,28 +641,39 @@ mod builtins {
|
||||
scope: crate::scope::Scope,
|
||||
#[allow(unused_variables)] mode: crate::compiler::Mode,
|
||||
func: &str,
|
||||
closure: Option<PyRef<PyTuple<PyCellRef>>>,
|
||||
) -> PyResult {
|
||||
// Determine code object:
|
||||
let code_obj = match source {
|
||||
#[cfg(feature = "rustpython-compiler")]
|
||||
Either::A(string) => {
|
||||
let source = string.as_str();
|
||||
vm.compile(source, mode, "<string>")
|
||||
.map_err(|err| vm.new_syntax_error(&err, Some(source)))?
|
||||
let mut opts = vm.compile_opts();
|
||||
if let Some(frame) = vm.current_frame() {
|
||||
opts.future_features = bytecode::CodeFlags::from_bits_truncate(
|
||||
frame.code.flags.bits() & compile_future_feature_mask().bits(),
|
||||
);
|
||||
}
|
||||
vm.compile_with_opts(source, mode, "<string>", opts)
|
||||
.map_err(|err| err.into_pyexception(vm, Some(source)))?
|
||||
}
|
||||
#[cfg(not(feature = "rustpython-compiler"))]
|
||||
Either::A(_) => return Err(vm.new_type_error(CODEGEN_NOT_SUPPORTED)),
|
||||
Either::B(code_obj) => code_obj,
|
||||
};
|
||||
|
||||
if !code_obj.freevars.is_empty() {
|
||||
vm.sys_module
|
||||
.get_attr("audit", vm)?
|
||||
.call((vm.ctx.new_str("exec"), code_obj.clone()), vm)?;
|
||||
|
||||
if closure.is_none() && !code_obj.freevars.is_empty() {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"code object passed to {func}() may not contain free variables"
|
||||
)));
|
||||
}
|
||||
|
||||
// Run the code:
|
||||
vm.run_code_obj(code_obj, scope)
|
||||
vm.run_code_obj_with_closure(code_obj, scope, closure)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
@@ -1002,8 +1033,8 @@ mod builtins {
|
||||
modulus,
|
||||
} = args;
|
||||
let modulus = modulus
|
||||
.as_ref()
|
||||
.map_or_else(|| vm.ctx.none.as_object(), |m| m);
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| vm.ctx.none.as_object());
|
||||
vm._pow(&x, &y, modulus)
|
||||
}
|
||||
|
||||
|
||||
@@ -751,7 +751,7 @@ pub mod sys {
|
||||
.read_to_string(&mut source)
|
||||
.map_err(|e| vm.new_os_error(format!("Error reading from stdin: {e}")))?;
|
||||
vm.compile(&source, crate::compiler::Mode::Single, "<stdin>")
|
||||
.map_err(|e| vm.new_os_error(format!("Error running stdin: {e}")))?;
|
||||
.map_err(|e| e.into_pyexception(vm, Some(&source)))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1228,7 +1228,7 @@ pub mod sys {
|
||||
vm.state.int_max_str_digits.store(maxdigits);
|
||||
Ok(())
|
||||
} else {
|
||||
let error = format!("maxdigits must be 0 or larger than {threshold:?}");
|
||||
let error = format!("maxdigits must be 0 or larger than {threshold}");
|
||||
Err(vm.new_value_error(error))
|
||||
}
|
||||
}
|
||||
@@ -1744,12 +1744,54 @@ pub mod sys {
|
||||
}
|
||||
|
||||
for hook in hooks {
|
||||
hook.call((event.clone(), args.clone()), vm)?;
|
||||
call_audit_hook(&hook, event.clone().into(), args, vm)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn audit_hook_can_trace(hook: &PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
|
||||
match hook.get_attr("__cantrace__", vm) {
|
||||
Ok(can_trace) => can_trace.try_to_bool(vm),
|
||||
Err(exc)
|
||||
if exc
|
||||
.class()
|
||||
.fast_issubclass(vm.ctx.exceptions.attribute_error) =>
|
||||
{
|
||||
Ok(false)
|
||||
}
|
||||
Err(exc) => Err(exc),
|
||||
}
|
||||
}
|
||||
|
||||
fn call_audit_hook(
|
||||
hook: &PyObjectRef,
|
||||
event: PyObjectRef,
|
||||
args: &PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
// Tracing is suppressed while dispatching Python audit hooks,
|
||||
// except for hooks that explicitly opt in with __cantrace__.
|
||||
vm.enter_tracing();
|
||||
let can_trace = audit_hook_can_trace(hook, vm);
|
||||
let result = match can_trace {
|
||||
Ok(can_trace) => {
|
||||
if can_trace {
|
||||
vm.leave_tracing();
|
||||
}
|
||||
let result = hook.call((event, args.clone()), vm).map(|_| ());
|
||||
if can_trace {
|
||||
vm.enter_tracing();
|
||||
}
|
||||
result
|
||||
}
|
||||
Err(exc) => Err(exc),
|
||||
};
|
||||
|
||||
vm.leave_tracing();
|
||||
result
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn audit(event: PyStrRef, args: PosArgs, vm: &VirtualMachine) -> PyResult<()> {
|
||||
if vm.audit_hooks.borrow().is_empty() {
|
||||
@@ -1773,10 +1815,13 @@ pub mod sys {
|
||||
let event: PyObjectRef = vm.ctx.new_str("sys.addaudithook").into();
|
||||
|
||||
for existing_hook in hooks {
|
||||
let Err(exc) = existing_hook.call((event.clone(), args.clone()), vm) else {
|
||||
let Err(exc) = call_audit_hook(&existing_hook, event.clone(), &args, vm) else {
|
||||
continue;
|
||||
};
|
||||
if exc.class().fast_issubclass(vm.ctx.exceptions.runtime_error) {
|
||||
if exc
|
||||
.class()
|
||||
.fast_issubclass(vm.ctx.exceptions.exception_type)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
return Err(exc);
|
||||
|
||||
@@ -747,7 +747,7 @@ fn fire(
|
||||
cb_extra: &[PyObjectRef],
|
||||
) -> PyResult<()> {
|
||||
// Prevent recursive event firing
|
||||
if FIRING.with(|f| f.get()) {
|
||||
if vm.tracing_is_suppressed() || FIRING.with(|f| f.get()) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -795,6 +795,7 @@ fn fire(
|
||||
let args = FuncArgs::from(args_vec);
|
||||
|
||||
FIRING.with(|f| f.set(true));
|
||||
vm.enter_tracing();
|
||||
let result = (|| {
|
||||
for (tool, cb) in callbacks {
|
||||
let result = cb.call(args.clone(), vm)?;
|
||||
@@ -817,6 +818,7 @@ fn fire(
|
||||
}
|
||||
Ok(())
|
||||
})();
|
||||
vm.leave_tracing();
|
||||
FIRING.with(|f| f.set(false));
|
||||
result
|
||||
}
|
||||
|
||||
+943
-24
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,83 @@
|
||||
use crate::bytecode;
|
||||
|
||||
pub(crate) const PY_SINGLE_INPUT: i32 = 256;
|
||||
pub(crate) const PY_FILE_INPUT: i32 = 257;
|
||||
pub(crate) const PY_EVAL_INPUT: i32 = 258;
|
||||
pub(crate) const PY_FUNC_TYPE_INPUT: i32 = 345;
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// `PyCF_*` compiler flags together with the `__future__` `CO_FUTURE_*`
|
||||
/// bits, mirroring `PyCompilerFlags.cf_flags`.
|
||||
///
|
||||
/// Caveat emptor: these flags are undocumented on purpose and depending on
|
||||
/// their effect outside the standard library is **unsupported**.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct CompilerFlags: i32 {
|
||||
const SOURCE_IS_UTF8 = 0x0100;
|
||||
const DONT_IMPLY_DEDENT = 0x0200;
|
||||
const ONLY_AST = 0x0400;
|
||||
const IGNORE_COOKIE = 0x0800;
|
||||
const TYPE_COMMENTS = 0x1000;
|
||||
const ALLOW_TOP_LEVEL_AWAIT = 0x2000;
|
||||
const ALLOW_INCOMPLETE_INPUT = 0x4000;
|
||||
const OPTIMIZED_AST = 0x8000 | Self::ONLY_AST.bits();
|
||||
|
||||
// __future__ flags - sync with Lib/__future__.py and Include/cpython/compile.h.
|
||||
const NESTED = 0x0010;
|
||||
const FUTURE_DIVISION = 0x20000;
|
||||
const FUTURE_ABSOLUTE_IMPORT = 0x40000;
|
||||
const FUTURE_WITH_STATEMENT = 0x80000;
|
||||
const FUTURE_PRINT_FUNCTION = 0x100000;
|
||||
const FUTURE_UNICODE_LITERALS = 0x200000;
|
||||
const FUTURE_BARRY_AS_BDFL = 0x400000;
|
||||
const FUTURE_GENERATOR_STOP = 0x800000;
|
||||
const FUTURE_ANNOTATIONS = 0x1000000;
|
||||
}
|
||||
}
|
||||
|
||||
impl CompilerFlags {
|
||||
const FUTURE_MASK: Self = Self::FUTURE_DIVISION
|
||||
.union(Self::FUTURE_ABSOLUTE_IMPORT)
|
||||
.union(Self::FUTURE_WITH_STATEMENT)
|
||||
.union(Self::FUTURE_PRINT_FUNCTION)
|
||||
.union(Self::FUTURE_UNICODE_LITERALS)
|
||||
.union(Self::FUTURE_BARRY_AS_BDFL)
|
||||
.union(Self::FUTURE_GENERATOR_STOP)
|
||||
.union(Self::FUTURE_ANNOTATIONS);
|
||||
const MASK_OBSOLETE: Self = Self::NESTED;
|
||||
const COMPILE_MASK: Self = Self::ONLY_AST
|
||||
.union(Self::ALLOW_TOP_LEVEL_AWAIT)
|
||||
.union(Self::TYPE_COMMENTS)
|
||||
.union(Self::DONT_IMPLY_DEDENT)
|
||||
.union(Self::ALLOW_INCOMPLETE_INPUT)
|
||||
.union(Self::OPTIMIZED_AST);
|
||||
pub(crate) const ALLOWED_FLAGS: Self = Self::FUTURE_MASK
|
||||
.union(Self::MASK_OBSOLETE)
|
||||
.union(Self::COMPILE_MASK);
|
||||
}
|
||||
|
||||
// Python-visible `ast.PyCF_*` attribute values. The flags cross the
|
||||
// `compile()` boundary as a plain `int`, so the exposed surface stays `i32`.
|
||||
pub(crate) const PY_CF_SOURCE_IS_UTF8: i32 = CompilerFlags::SOURCE_IS_UTF8.bits();
|
||||
pub(crate) const PY_CF_DONT_IMPLY_DEDENT: i32 = CompilerFlags::DONT_IMPLY_DEDENT.bits();
|
||||
pub(crate) const PY_CF_ONLY_AST: i32 = CompilerFlags::ONLY_AST.bits();
|
||||
pub(crate) const PY_CF_IGNORE_COOKIE: i32 = CompilerFlags::IGNORE_COOKIE.bits();
|
||||
pub(crate) const PY_CF_TYPE_COMMENTS: i32 = CompilerFlags::TYPE_COMMENTS.bits();
|
||||
pub(crate) const PY_CF_ALLOW_TOP_LEVEL_AWAIT: i32 = CompilerFlags::ALLOW_TOP_LEVEL_AWAIT.bits();
|
||||
pub(crate) const PY_CF_ALLOW_INCOMPLETE_INPUT: i32 = CompilerFlags::ALLOW_INCOMPLETE_INPUT.bits();
|
||||
pub(crate) const PY_CF_OPTIMIZED_AST: i32 = CompilerFlags::OPTIMIZED_AST.bits();
|
||||
|
||||
pub(crate) fn compile_future_feature_mask() -> bytecode::CodeFlags {
|
||||
// RustPython accepts barry_as_FLUFL but leaves its parser mode disabled.
|
||||
bytecode::CodeFlags::FUTURE_DIVISION
|
||||
| bytecode::CodeFlags::FUTURE_ABSOLUTE_IMPORT
|
||||
| bytecode::CodeFlags::FUTURE_WITH_STATEMENT
|
||||
| bytecode::CodeFlags::FUTURE_PRINT_FUNCTION
|
||||
| bytecode::CodeFlags::FUTURE_UNICODE_LITERALS
|
||||
| bytecode::CodeFlags::FUTURE_GENERATOR_STOP
|
||||
| bytecode::CodeFlags::FUTURE_ANNOTATIONS
|
||||
}
|
||||
|
||||
pub(crate) fn compile_future_features_from_flags(flags: i32) -> bytecode::CodeFlags {
|
||||
bytecode::CodeFlags::from_bits_truncate(flags as u32 & compile_future_feature_mask().bits())
|
||||
}
|
||||
@@ -300,8 +300,10 @@ impl Default for InterpreterBuilder {
|
||||
/// let scope = vm.new_scope_with_builtins();
|
||||
/// let source = r#"print("Hello World!")"#;
|
||||
/// let code_obj = vm.compile(
|
||||
/// source, Mode::Exec, "<embedded>"
|
||||
/// ).map_err(|err| vm.new_syntax_error(&err, Some(source))).unwrap();
|
||||
/// source,
|
||||
/// Mode::Exec,
|
||||
/// "<embedded>",
|
||||
/// ).map_err(|err| err.into_pyexception(vm, Some(source))).unwrap();
|
||||
/// vm.run_code_obj(code_obj, scope).unwrap();
|
||||
/// });
|
||||
/// ```
|
||||
|
||||
+47
-26
@@ -5,6 +5,9 @@
|
||||
|
||||
#[cfg(feature = "rustpython-compiler")]
|
||||
mod compile;
|
||||
pub(crate) mod compile_mode;
|
||||
#[cfg(feature = "rustpython-compiler")]
|
||||
pub use compile::VmCompileError;
|
||||
mod context;
|
||||
mod interpreter;
|
||||
mod method;
|
||||
@@ -82,6 +85,7 @@ pub struct VirtualMachine {
|
||||
pub profile_func: RefCell<PyObjectRef>,
|
||||
pub trace_func: RefCell<PyObjectRef>,
|
||||
pub use_tracing: Cell<bool>,
|
||||
tracing_depth: Cell<usize>,
|
||||
pub recursion_limit: Cell<usize>,
|
||||
pub(crate) signal_handlers: OnceCell<SignalHandlers>,
|
||||
pub(crate) signal_rx: Option<signal::UserSignalReceiver>,
|
||||
@@ -739,6 +743,7 @@ impl VirtualMachine {
|
||||
profile_func,
|
||||
trace_func,
|
||||
use_tracing: Cell::new(false),
|
||||
tracing_depth: Cell::new(0),
|
||||
recursion_limit: Cell::new(if cfg!(debug_assertions) { 256 } else { 1000 }),
|
||||
signal_handlers,
|
||||
signal_rx: None,
|
||||
@@ -1095,30 +1100,22 @@ impl VirtualMachine {
|
||||
}
|
||||
|
||||
pub fn run_code_obj(&self, code: PyRef<PyCode>, scope: Scope) -> PyResult {
|
||||
use crate::builtins::{PyFunction, PyModule};
|
||||
self.run_code_obj_with_closure(code, scope, None)
|
||||
}
|
||||
|
||||
// Create a function object for module code, similar to CPython's PyEval_EvalCode
|
||||
let func = PyFunction::new(code.clone(), scope.globals.clone(), self)?;
|
||||
let func_obj = func.into_ref(&self.ctx).into();
|
||||
pub(crate) fn run_code_obj_with_closure(
|
||||
&self,
|
||||
code: PyRef<PyCode>,
|
||||
scope: Scope,
|
||||
closure: Option<PyRef<crate::builtins::PyTuple<crate::builtins::function::PyCellRef>>>,
|
||||
) -> PyResult {
|
||||
use crate::builtins::PyFunction;
|
||||
|
||||
// Extract builtins from globals["__builtins__"], like PyEval_EvalCode
|
||||
let builtins = match scope
|
||||
.globals
|
||||
.get_item_opt(identifier!(self, __builtins__), self)?
|
||||
{
|
||||
Some(b) => {
|
||||
if let Some(module) = b.downcast_ref::<PyModule>() {
|
||||
module.dict().into()
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
None => self.builtins.dict().into(),
|
||||
};
|
||||
|
||||
let frame =
|
||||
Frame::new(code, scope, builtins, &[], Some(func_obj), false, self).into_ref(&self.ctx);
|
||||
self.run_frame(frame)
|
||||
// Create a function object for module code, similar to PyEval_EvalCode
|
||||
let mut func = PyFunction::new(code, scope.globals.clone(), self)?;
|
||||
func.closure = closure;
|
||||
let func = func.into_ref(&self.ctx);
|
||||
func.invoke_with_locals(FuncArgs::default(), scope.locals, self)
|
||||
}
|
||||
|
||||
#[cold]
|
||||
@@ -1434,9 +1431,11 @@ impl VirtualMachine {
|
||||
}
|
||||
|
||||
/// Stack margin bytes (like _PyOS_STACK_MARGIN_BYTES).
|
||||
/// 2048 * sizeof(void*) = 16KB for 64-bit.
|
||||
/// The margin is doubled for debug/sanitized builds because frame
|
||||
/// evaluation consumes more native stack in those configurations.
|
||||
#[cfg_attr(any(miri, target_env = "musl"), allow(dead_code))]
|
||||
const STACK_MARGIN_BYTES: usize = 2048 * core::mem::size_of::<usize>();
|
||||
const STACK_MARGIN_BYTES: usize =
|
||||
(if cfg!(debug_assertions) { 4096 } else { 2048 }) * core::mem::size_of::<usize>();
|
||||
|
||||
/// Get the stack boundaries using platform-specific APIs.
|
||||
/// Returns (base, top) where base is the lowest address and top is the highest.
|
||||
@@ -1709,11 +1708,33 @@ impl VirtualMachine {
|
||||
#[cfg(feature = "rustpython-codegen")]
|
||||
pub fn compile_opts(&self) -> crate::compiler::CompileOpts {
|
||||
crate::compiler::CompileOpts {
|
||||
optimize: self.state.config.settings.optimize,
|
||||
optimize: self.state.config.settings.optimize.min(2),
|
||||
debug_ranges: self.state.config.settings.code_debug_ranges,
|
||||
int_max_str_digits: self.state.int_max_str_digits.load(),
|
||||
allow_top_level_await: false,
|
||||
future_features: crate::bytecode::CodeFlags::empty(),
|
||||
dont_imply_dedent: false,
|
||||
recursion_limit: self.recursion_limit.get(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn enter_tracing(&self) {
|
||||
self.tracing_depth.set(self.tracing_depth.get() + 1);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn leave_tracing(&self) {
|
||||
let depth = self.tracing_depth.get();
|
||||
debug_assert!(depth > 0);
|
||||
self.tracing_depth.set(depth.saturating_sub(1));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn tracing_is_suppressed(&self) -> bool {
|
||||
self.tracing_depth.get() != 0
|
||||
}
|
||||
|
||||
// To be called right before raising the recursion depth.
|
||||
fn check_recursive_call(&self, _where: &str) -> PyResult<()> {
|
||||
if self.recursion_depth.get() >= self.recursion_limit.get() {
|
||||
@@ -2286,7 +2307,7 @@ mod tests {
|
||||
let source = "from dir_module.dir_module_inner import value2";
|
||||
let code_obj = vm
|
||||
.compile(source, vm::compiler::Mode::Exec, "<embedded>")
|
||||
.map_err(|err| vm.new_syntax_error(&err, Some(source)))
|
||||
.map_err(|err| err.into_pyexception(vm, Some(source)))
|
||||
.unwrap();
|
||||
|
||||
if let Err(e) = vm.run_code_obj(code_obj, scope) {
|
||||
|
||||
@@ -22,7 +22,7 @@ impl VirtualMachine {
|
||||
pub fn run_string(&self, scope: Scope, source: &str, source_path: &str) -> PyResult {
|
||||
let code_obj = self
|
||||
.compile(source, compiler::Mode::Exec, source_path)
|
||||
.map_err(|err| self.new_syntax_error(&err, Some(source)))?;
|
||||
.map_err(|err| err.into_pyexception(self, Some(source)))?;
|
||||
// linecache._register_code(code, source, filename)
|
||||
let _ = self.register_code_in_linecache(&code_obj, source);
|
||||
self.run_code_obj(code_obj, scope)
|
||||
@@ -47,7 +47,7 @@ impl VirtualMachine {
|
||||
pub fn run_block_expr(&self, scope: Scope, source: &str) -> PyResult {
|
||||
let code_obj = self
|
||||
.compile(source, compiler::Mode::BlockExpr, "<embedded>")
|
||||
.map_err(|err| self.new_syntax_error(&err, Some(source)))?;
|
||||
.map_err(|err| err.into_pyexception(self, Some(source)))?;
|
||||
self.run_code_obj(code_obj, scope)
|
||||
}
|
||||
}
|
||||
@@ -105,11 +105,19 @@ mod file_run {
|
||||
if path != "<stdin>" {
|
||||
set_main_loader(module_dict, path, "SourceFileLoader", self)?;
|
||||
}
|
||||
match crate::host_env::fs::read_to_string(path) {
|
||||
Ok(source) => {
|
||||
match crate::host_env::fs::read(path) {
|
||||
Ok(source_bytes) => {
|
||||
if source_bytes.contains(&0) {
|
||||
return Err(self.new_exception_msg(
|
||||
self.ctx.exceptions.syntax_error.to_owned(),
|
||||
"source code cannot contain null bytes".into(),
|
||||
));
|
||||
}
|
||||
let source = String::from_utf8(source_bytes)
|
||||
.map_err(|err| self.new_os_error(err.to_string()))?;
|
||||
let code_obj = self
|
||||
.compile(&source, compiler::Mode::Exec, path)
|
||||
.map_err(|err| self.new_syntax_error(&err, Some(&source)))?;
|
||||
.map_err(|err| err.into_pyexception(self, Some(&source)))?;
|
||||
self.run_code_obj(code_obj, scope)?;
|
||||
}
|
||||
Err(err) => {
|
||||
|
||||
@@ -719,6 +719,7 @@ impl VirtualMachine {
|
||||
profile_func: RefCell::new(global_profile.unwrap_or_else(|| self.ctx.none())),
|
||||
trace_func: RefCell::new(global_trace.unwrap_or_else(|| self.ctx.none())),
|
||||
use_tracing: Cell::new(use_tracing),
|
||||
tracing_depth: Cell::new(0),
|
||||
recursion_limit: self.recursion_limit.clone(),
|
||||
signal_handlers: core::cell::OnceCell::new(),
|
||||
signal_rx: None,
|
||||
|
||||
+82
-24
@@ -54,15 +54,6 @@ impl SyntaxErrorInfo {
|
||||
Self { msg, narrow_caret }
|
||||
}
|
||||
|
||||
fn with_msg(&mut self, msg: &str) {
|
||||
self.msg = msg.into();
|
||||
}
|
||||
|
||||
#[cfg(feature = "parser")]
|
||||
const fn with_narrow_caret(&mut self, narrow_caret: bool) {
|
||||
self.narrow_caret = narrow_caret;
|
||||
}
|
||||
|
||||
#[cfg(feature = "parser")]
|
||||
#[must_use]
|
||||
const fn handle_expected_token(expected: TokenKind, found: TokenKind) -> &'static str {
|
||||
@@ -114,7 +105,7 @@ impl SyntaxErrorInfo {
|
||||
}
|
||||
|
||||
ParseErrorType::InvalidStarredExpressionUsage => {
|
||||
self.with_narrow_caret(true);
|
||||
self.narrow_caret = true;
|
||||
"invalid syntax".into()
|
||||
}
|
||||
|
||||
@@ -134,7 +125,7 @@ impl SyntaxErrorInfo {
|
||||
ParseErrorType::EmptyTypeParams => "Type parameter list cannot be empty".into(),
|
||||
|
||||
ParseErrorType::InvalidStarPatternUsage => {
|
||||
self.with_narrow_caret(true);
|
||||
self.narrow_caret = true;
|
||||
"cannot use starred expression here".into()
|
||||
}
|
||||
|
||||
@@ -179,12 +170,22 @@ impl SyntaxErrorInfo {
|
||||
| ParseErrorType::SimpleAndCompoundStatementOnSameLine
|
||||
| ParseErrorType::ExpectedExpression => "invalid syntax".into(),
|
||||
|
||||
ParseErrorType::OtherError(s) if s.starts_with("Expected an identifier") => {
|
||||
"invalid syntax".into()
|
||||
}
|
||||
|
||||
ParseErrorType::OtherError(s)
|
||||
if s.starts_with("Expected an identifier, but found a keyword") =>
|
||||
if s.eq_ignore_ascii_case(
|
||||
"Expected a type parameter or the end of the type parameter list",
|
||||
) =>
|
||||
{
|
||||
"invalid syntax".into()
|
||||
}
|
||||
|
||||
ParseErrorType::OtherError(s) if s.eq_ignore_ascii_case("Expected a statement") => {
|
||||
"invalid syntax".into()
|
||||
}
|
||||
|
||||
ParseErrorType::OtherError(s)
|
||||
if s.eq_ignore_ascii_case(
|
||||
"bytes literal cannot be mixed with non-bytes literals",
|
||||
@@ -262,7 +263,7 @@ impl SyntaxErrorInfo {
|
||||
_ => return,
|
||||
};
|
||||
|
||||
self.with_msg(&msg);
|
||||
self.msg = msg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,6 +577,16 @@ impl VirtualMachine {
|
||||
source: Option<&str>,
|
||||
allow_incomplete: bool,
|
||||
) -> PyBaseExceptionRef {
|
||||
if matches!(
|
||||
error,
|
||||
crate::compiler::CompileError::Codegen(crate::compiler::codegen::error::CodegenError {
|
||||
error: crate::compiler::codegen::error::CodegenErrorType::RecursionError,
|
||||
..
|
||||
})
|
||||
) {
|
||||
return self.new_recursion_error(error.to_string());
|
||||
}
|
||||
|
||||
let incomplete_or_syntax = |allow| -> &'static Py<crate::builtins::PyType> {
|
||||
if allow {
|
||||
self.ctx.exceptions.incomplete_input_error
|
||||
@@ -687,7 +698,9 @@ impl VirtualMachine {
|
||||
raw_location,
|
||||
..
|
||||
}) => {
|
||||
if s.starts_with("Expected an indented block after") {
|
||||
if s.starts_with("Expected an indented block after")
|
||||
|| s.starts_with("expected an indented block after")
|
||||
{
|
||||
if allow_incomplete {
|
||||
// Check that all chars in the error are whitespace, if so, the source is
|
||||
// incomplete. Otherwise, we've found code that might violates
|
||||
@@ -717,6 +730,12 @@ impl VirtualMachine {
|
||||
} else {
|
||||
self.ctx.exceptions.indentation_error
|
||||
}
|
||||
} else if allow_incomplete
|
||||
&& source.is_some_and(|source| {
|
||||
raw_location.end().to_usize() >= source.len() && !source.ends_with('\n')
|
||||
})
|
||||
{
|
||||
self.ctx.exceptions.incomplete_input_error
|
||||
} else {
|
||||
self.ctx.exceptions.syntax_error
|
||||
}
|
||||
@@ -738,7 +757,29 @@ impl VirtualMachine {
|
||||
let statement = source.and_then(|src| get_statement(src, error.location()));
|
||||
|
||||
let mut msg = error.to_string();
|
||||
if let Some(msg) = msg.get_mut(..1) {
|
||||
if !msg.starts_with("Exceeds the limit ")
|
||||
&& !msg.starts_with("Did you mean ")
|
||||
&& !msg.starts_with("Invalid star expression")
|
||||
&& !msg.starts_with("Function parameters cannot be parenthesized")
|
||||
&& !msg.starts_with("Lambda expression parameters cannot be parenthesized")
|
||||
&& !msg.starts_with("Cannot have two type comments on def")
|
||||
&& !msg.starts_with("Variable annotation syntax is")
|
||||
&& !msg.starts_with("The '@' operator is")
|
||||
&& !msg.starts_with("Async functions are")
|
||||
&& !msg.starts_with("Async comprehensions are")
|
||||
&& !msg.starts_with("Async for loops are")
|
||||
&& !msg.starts_with("Async with statements are")
|
||||
&& !msg.starts_with("Exception groups are")
|
||||
&& !msg.starts_with("Positional-only parameters are")
|
||||
&& !msg.starts_with("Pattern matching is")
|
||||
&& !msg.starts_with("Type statement is")
|
||||
&& !msg.starts_with("Type parameter lists are")
|
||||
&& !msg.starts_with("Type parameter defaults are")
|
||||
&& !msg.starts_with("Assignment expressions are")
|
||||
&& !msg.starts_with("Await expressions are")
|
||||
&& !msg.starts_with("Underscores in numeric literals are")
|
||||
&& let Some(msg) = msg.get_mut(..1)
|
||||
{
|
||||
msg.make_ascii_lowercase();
|
||||
}
|
||||
|
||||
@@ -753,17 +794,26 @@ impl VirtualMachine {
|
||||
};
|
||||
|
||||
if syntax_error_type.is(self.ctx.exceptions.tab_error) {
|
||||
syntax_error_info.with_msg("inconsistent use of tabs and spaces in indentation");
|
||||
syntax_error_info.msg = "inconsistent use of tabs and spaces in indentation".to_owned();
|
||||
}
|
||||
if syntax_error_type.is(self.ctx.exceptions.incomplete_input_error) {
|
||||
syntax_error_info.msg = "incomplete input".to_owned();
|
||||
}
|
||||
|
||||
let SyntaxErrorInfo { msg, narrow_caret } = syntax_error_info;
|
||||
let check_version_suite_error = msg.starts_with("Async functions are")
|
||||
|| msg.starts_with("Async for loops are")
|
||||
|| msg.starts_with("Async with statements are")
|
||||
|| msg.starts_with("Exception groups are")
|
||||
|| msg.starts_with("except expressions without parentheses are")
|
||||
|| msg.starts_with("Pattern matching is");
|
||||
let line_end_binary_operator_error = msg.starts_with("The '@' operator is");
|
||||
|
||||
let syntax_error = self.new_exception_msg(syntax_error_type, msg.into());
|
||||
|
||||
let (lineno, offset) = error.python_location();
|
||||
let lineno = self.ctx.new_int(lineno);
|
||||
let offset = self.ctx.new_int(offset);
|
||||
|
||||
let (lineno_raw, offset_raw) = error.python_location();
|
||||
let lineno = self.ctx.new_int(lineno_raw);
|
||||
let offset = self.ctx.new_int(offset_raw);
|
||||
set_attrs!(
|
||||
syntax_error.as_object(), self, unwrap,
|
||||
"lineno" => lineno,
|
||||
@@ -772,15 +822,23 @@ impl VirtualMachine {
|
||||
|
||||
// Set end_lineno and end_offset if available
|
||||
if let Some((end_lineno, end_offset)) = error.python_end_location() {
|
||||
let (end_lineno, end_offset) = if narrow_caret {
|
||||
let (end_lineno, end_offset) = if check_version_suite_error
|
||||
&& statement
|
||||
.as_deref()
|
||||
.and_then(|line| line.chars().next())
|
||||
.is_some_and(|ch| ch.is_ascii_whitespace())
|
||||
{
|
||||
(end_lineno, -1)
|
||||
} else if line_end_binary_operator_error && end_offset == offset_raw {
|
||||
(end_lineno, (end_offset + 1) as isize)
|
||||
} else if narrow_caret {
|
||||
let (l, o) = error.python_location();
|
||||
(l, o + 1)
|
||||
(l, (o + 1) as isize)
|
||||
} else {
|
||||
(end_lineno, end_offset)
|
||||
(end_lineno, end_offset as isize)
|
||||
};
|
||||
let end_lineno = self.ctx.new_int(end_lineno);
|
||||
let end_offset = self.ctx.new_int(end_offset);
|
||||
|
||||
set_attrs!(
|
||||
syntax_error.as_object(), self, unwrap,
|
||||
"end_lineno" => end_lineno,
|
||||
|
||||
@@ -20,6 +20,7 @@ no-start-func = []
|
||||
rustpython-common = { workspace = true }
|
||||
rustpython-pylib = { workspace = true, optional = true }
|
||||
rustpython-stdlib = { workspace = true, default-features = false, optional = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
# make sure no threading! otherwise wasm build will fail
|
||||
rustpython-vm = { workspace = true, features = ["compiler", "encodings", "serde", "wasmbind"] }
|
||||
|
||||
|
||||
@@ -70,7 +70,11 @@ pub mod eval {
|
||||
if let Some(js_vars) = js_vars {
|
||||
vm.add_to_scope("js_vars".into(), js_vars.into())?;
|
||||
}
|
||||
vm.run(source, mode, None)
|
||||
if matches!(mode, Mode::Single) {
|
||||
vm.exec_single(source, None)
|
||||
} else {
|
||||
vm.run(source, mode, None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate Python code
|
||||
|
||||
@@ -6,9 +6,14 @@ use crate::{
|
||||
use alloc::rc::{Rc, Weak};
|
||||
use core::cell::RefCell;
|
||||
use js_sys::{Object, TypeError};
|
||||
use ruff_text_size::Ranged;
|
||||
use rustpython_vm::{
|
||||
Interpreter, PyObjectRef, PyRef, PyResult, Settings, VirtualMachine, builtins::PyWeak,
|
||||
compiler::Mode, function::ArgMapping, scope::Scope,
|
||||
Interpreter, PyObjectRef, PyRef, PyResult, Settings, VirtualMachine,
|
||||
builtins::PyWeak,
|
||||
compiler::{self, Mode},
|
||||
function::ArgMapping,
|
||||
scope::Scope,
|
||||
vm::VmCompileError,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen::prelude::*;
|
||||
@@ -21,6 +26,25 @@ pub(crate) struct StoredVirtualMachine {
|
||||
held_objects: RefCell<Vec<PyObjectRef>>,
|
||||
}
|
||||
|
||||
fn compile_err_to_js(vm: &VirtualMachine, err: VmCompileError) -> JsValue {
|
||||
match err {
|
||||
VmCompileError::Compile(err) => convert::syntax_err(err).into(),
|
||||
err => convert::py_err_to_js_err(vm, &err.into_pyexception(vm, None)),
|
||||
}
|
||||
}
|
||||
|
||||
fn statement_chunks(source: &str) -> Option<Vec<&str>> {
|
||||
let module = compiler::parser::parse_module(source).ok()?.into_syntax();
|
||||
module
|
||||
.body
|
||||
.iter()
|
||||
.map(|stmt| {
|
||||
let range = stmt.range();
|
||||
source.get(range.start().to_usize()..range.end().to_usize())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
mod _window {
|
||||
use super::{js_module, wasm_builtins};
|
||||
@@ -263,8 +287,8 @@ impl WASMVirtualMachine {
|
||||
) -> Result<(), JsValue> {
|
||||
self.with_vm(|vm, _| {
|
||||
let code = vm
|
||||
.compile(source, Mode::Exec, &name)
|
||||
.map_err(convert::syntax_err)?;
|
||||
.compile(source, Mode::Exec, name.as_str())
|
||||
.map_err(|err| compile_err_to_js(vm, err))?;
|
||||
let attrs = vm.ctx.new_dict();
|
||||
attrs
|
||||
.set_item("__name__", vm.new_pyobj(name.as_str()), vm)
|
||||
@@ -327,13 +351,46 @@ impl WASMVirtualMachine {
|
||||
) -> Result<JsValue, JsValue> {
|
||||
self.with_vm(|vm, StoredVirtualMachine { scope, .. }| {
|
||||
let source_path = source_path.unwrap_or_else(|| "<wasm>".to_owned());
|
||||
let code = vm.compile(source, mode, &source_path);
|
||||
let code = code.map_err(convert::syntax_err)?;
|
||||
let code = vm.compile(source, mode, source_path.as_str());
|
||||
let code = code.map_err(|err| compile_err_to_js(vm, err))?;
|
||||
let result = vm.run_code_obj(code, scope.clone());
|
||||
convert::pyresult_to_js_result(vm, result)
|
||||
})?
|
||||
}
|
||||
|
||||
pub(crate) fn run_single(
|
||||
&self,
|
||||
source: &str,
|
||||
source_path: Option<String>,
|
||||
) -> Result<JsValue, JsValue> {
|
||||
self.with_vm(|vm, StoredVirtualMachine { scope, .. }| {
|
||||
let source_path = source_path.unwrap_or_else(|| "<wasm>".to_owned());
|
||||
let Some(chunks) = statement_chunks(source) else {
|
||||
let code = vm.compile(source, Mode::Single, source_path.as_str());
|
||||
let code = code.map_err(|err| compile_err_to_js(vm, err))?;
|
||||
let result = vm.run_code_obj(code, scope.clone());
|
||||
return convert::pyresult_to_js_result(vm, result);
|
||||
};
|
||||
|
||||
if chunks.is_empty() {
|
||||
return Ok(convert::py_to_js(vm, vm.ctx.none()));
|
||||
}
|
||||
|
||||
let displayhook = vm
|
||||
.sys_module
|
||||
.get_attr("displayhook", vm)
|
||||
.map_err(|_| TypeError::new("lost sys.displayhook"))?;
|
||||
let mut result = vm.ctx.none();
|
||||
for chunk in chunks {
|
||||
let code = vm.compile(chunk, Mode::BlockExpr, source_path.as_str());
|
||||
let code = code.map_err(|err| compile_err_to_js(vm, err))?;
|
||||
result = vm.run_code_obj(code, scope.clone()).into_js(vm)?;
|
||||
displayhook.call((result.clone(),), vm).into_js(vm)?;
|
||||
}
|
||||
Ok(convert::py_to_js(vm, result))
|
||||
})?
|
||||
}
|
||||
|
||||
pub fn exec(&self, source: &str, source_path: Option<String>) -> Result<JsValue, JsValue> {
|
||||
self.run(source, Mode::Exec, source_path)
|
||||
}
|
||||
@@ -348,6 +405,6 @@ impl WASMVirtualMachine {
|
||||
source: &str,
|
||||
source_path: Option<String>,
|
||||
) -> Result<JsValue, JsValue> {
|
||||
self.run(source, Mode::Single, source_path)
|
||||
self.run_single(source, source_path)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ fn main() -> vm::PyResult<()> {
|
||||
let source = r#"print("Hello World!")"#;
|
||||
let code_obj = vm
|
||||
.compile(source, vm::compiler::Mode::Exec, "<embedded>")
|
||||
.map_err(|err| vm.new_syntax_error(&err, Some(source)))?;
|
||||
.map_err(|err| err.into_pyexception(vm, Some(source)))?;
|
||||
|
||||
vm.run_code_obj(code_obj, scope)?;
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ def fib(n):
|
||||
// (note that this is only the case when compiler::Mode::Single is passed to vm.compile)
|
||||
match vm
|
||||
.compile(&input, vm::compiler::Mode::Single, "<embedded>")
|
||||
.map_err(|err| vm.new_syntax_error(&err, Some(&input)))
|
||||
.map_err(|err| err.into_pyexception(vm, Some(&input)))
|
||||
.and_then(|code_obj| vm.run_code_obj(code_obj, scope.clone()))
|
||||
{
|
||||
Ok(output) => {
|
||||
|
||||
Loaded 100 of 105 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user