Compare commits

...

34 Commits

Author SHA1 Message Date
Jeong YunWon
b7a7b6b923 remove warnings from wasm build 2025-01-13 15:06:29 +09:00
Jeong YunWon
0e00d2328d wasm32-wasi -> wasm32-wasip1 2025-01-13 15:06:29 +09:00
Shubham Patil
53db70e784 Support recursion in JIT-ed functions (#5473) 2025-01-13 14:55:27 +09:00
Sacha Dupuydauby
76c699b4ba Update contextlib from CPython 3.12 2025-01-12 00:40:41 +09:00
Noa
c901bc07a4 Upgrade wasm deps + fix demo 2025-01-11 18:48:27 +09:00
Noa
b7db23bbae Fix warnings for Rust 1.84 2025-01-11 18:48:27 +09:00
Jeong, YunWon
389b20d977 Merge pull request #5444 from key262yek/update_fstring_from_v3.12.7
Update fstring from v3.12.7
2025-01-10 10:44:31 +09:00
Bob McWhirter
d06459fa49 guard signal-handling init more broadly
If `install_signal_handlers` is false (due to embedding),
the VM still inits the signal stdlib and installs a lot
of signal-handling, including touching *ever* signal,
including SIGINT.

When running multiple concurrent interpreters with
varying inits at varying times, this can break the
hosting application's signal-handling so lovingly
set up before starting anything with RustPython.
2025-01-09 16:21:25 -06:00
Shubham Patil
e2a55cbf34 Handle pre-release flag being empty for schedule triggers in release workflow
GitHub workflow_dispatch input variables are always empty for other triggers
2025-01-09 17:31:02 +09:00
Jeong, YunWon
a5e6ade9cb Merge pull request #5454 from coolreader18/rust-1.83
Bump MSRV to 1.83
2025-01-07 13:13:42 +09:00
Jeong, YunWon
a1e32566d3 Merge pull request #5469 from fu050409/patch-1 2025-01-07 12:38:08 +09:00
Noa
8c7bfb3e1a Fix redox 2025-01-06 13:09:49 -06:00
苏向夜
bb0480e978 docs(readme): fix installation command for cargo 2025-01-07 00:52:16 +08:00
Jeong, YunWon
2ccc745513 Merge pull request #5465 from crazymerlyn/caseless-bump 2025-01-04 11:43:30 +09:00
Jeong, YunWon
bea83fe94d Merge pull request #5466 from theshubhamp/gh-release 2025-01-04 11:43:10 +09:00
Shubham Patil
3feaf689d8 Re-enable Release Notes Generation 2025-01-03 21:49:17 +05:30
Shubham Patil
bd627b58af Schedule Pre-Release on Monday 9AM UTC 2025-01-03 21:49:17 +05:30
Shubham Patil
c561d33cb2 Support both Release & Pre-Release in Release Workflow
On Push "main" is removed
2025-01-03 21:49:17 +05:30
Ashwin Naren
c8fd3bd683 Build wasm on release 2025-01-03 14:56:58 +09:00
Ankit Goel
fef1e31634 Bump rust-caseless to 0.2.2 2024-12-31 12:26:29 +00:00
Shubham Patil
1abaf87abe Temporarily disable release notes generation
Release creation fails with this error:
```
HTTP 422: Validation Failed (https://api.github.com/repos/RustPython/RustPython/releases)
body is too long (maximum is 125000 characters)
Error: Process completed with exit code 1.
```

Most likely because there's no previous release to diff of from.

Disabling, getting a green release & enabling back might fix this.
2024-12-30 16:44:55 +09:00
Shubham Patil
38593fbd85 Check operand types in bool or, and, xor to be PyInt (#5461)
* Added Tests for Bitwise or, and, xor type error

* Sync binary operator order comment with actual implementation

* Check operand types in bool or, and, xor to be PyInt

PyNumber methods are expected to type check both arguments.

Dispatch is not done by inverting parameter order for __r<op>__ (example __ror__) when calls are handled via PyNumberMethods
2024-12-30 16:44:27 +09:00
Ankit Goel
8d187fd275 Bump result-like to 0.5.0 2024-12-28 11:38:00 +09:00
Shubham Patil
646cc81656 Add GitHub Binary Release Pipeline for RustPython (#5456) 2024-12-28 11:34:53 +09:00
carsonzhu
01f7536b36 expose run_shell 2024-12-11 17:33:36 +09:00
Jeong, YunWon
97e5ec02f8 Merge pull request #5449 from key262yek/update_test_float_from_CPython_v3.12.7
Update test float from c python v3.12.7
2024-12-06 12:50:55 +09:00
Oskar Skog
3dced01af0 Move os.system from posix.rs to os.rs
Fixes #5100
2024-12-06 12:19:34 +09:00
0cf4534c5c copy new file "Lib/test/support/testcase.py" for test_float.py
Some checks failed
CI / Run rust tests (macos-latest) (pull_request) Has been cancelled
CI / Run rust tests (windows-latest) (pull_request) Has been cancelled
CI / Ensure compilation on various targets (pull_request) Has been cancelled
CI / Run snippets and cpython tests (macos-latest) (pull_request) Has been cancelled
CI / Run snippets and cpython tests (ubuntu-latest) (pull_request) Has been cancelled
CI / Run snippets and cpython tests (windows-latest) (pull_request) Has been cancelled
CI / Check Rust code with rustfmt and clippy (pull_request) Has been cancelled
CI / Run tests under miri (pull_request) Has been cancelled
CI / Check the WASM package and demo (pull_request) Has been cancelled
CI / Run snippets and cpython tests on wasm-wasi (pull_request) Has been cancelled
CI / Run rust tests (ubuntu-latest) (pull_request) Has been cancelled
2024-12-05 15:29:35 +09:00
044f66fba3 copy from cpython v3.12.7 2024-12-05 15:29:35 +09:00
40a9ddad4e update test_fstring.py from cpython 3.12.7
add expectedFailure to tag what should rustpython do
add comment for some syntaxerror which make test run broken
2024-12-05 15:04:56 +09:00
Noa
8ac7e34be2 Updates for Rust 1.83 2024-12-03 17:05:24 -06:00
Noa
c883f0ad8a Updates for Rust 1.82 2024-10-17 16:32:47 -05:00
Noa
eae60113af Update some stuff for inline const & associated type bounds 2024-10-17 16:32:17 -05:00
Noa
1aab5240cf Update for rust 1.77 2024-10-17 16:32:17 -05:00
77 changed files with 1970 additions and 1035 deletions

View File

@@ -229,6 +229,7 @@ jobs:
uses: coolreader18/redoxer-action@v1
with:
command: check
args: --ignore-rust-version
snippets_cpython:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
@@ -407,7 +408,7 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
target: wasm32-wasi
target: wasm32-wasip1
- uses: Swatinem/rust-cache@v2
- name: Setup Wasmer
@@ -415,8 +416,8 @@ jobs:
- name: Install clang
run: sudo apt-get update && sudo apt-get install clang -y
- name: build rustpython
run: cargo build --release --target wasm32-wasi --features freeze-stdlib,stdlib --verbose
run: cargo build --release --target wasm32-wasip1 --features freeze-stdlib,stdlib --verbose
- name: run snippets
run: wasmer run --dir `pwd` target/wasm32-wasi/release/rustpython.wasm -- `pwd`/extra_tests/snippets/stdlib_random.py
run: wasmer run --dir `pwd` target/wasm32-wasip1/release/rustpython.wasm -- `pwd`/extra_tests/snippets/stdlib_random.py
- name: run cpython unittest
run: wasmer run --dir `pwd` target/wasm32-wasi/release/rustpython.wasm -- `pwd`/Lib/test/test_int.py
run: wasmer run --dir `pwd` target/wasm32-wasip1/release/rustpython.wasm -- `pwd`/Lib/test/test_int.py

145
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,145 @@
name: Release
on:
schedule:
# 9 AM UTC on every Monday
- cron: "0 9 * * Mon"
workflow_dispatch:
inputs:
pre-release:
type: boolean
description: Mark "Pre-Release"
required: false
default: true
permissions:
contents: write
env:
CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,sqlite,ssl
jobs:
build:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
# - runner: ubuntu-latest
# target: i686-unknown-linux-gnu
# - runner: ubuntu-latest
# target: aarch64-unknown-linux-gnu
# - runner: ubuntu-latest
# target: armv7-unknown-linux-gnueabi
# - runner: ubuntu-latest
# target: s390x-unknown-linux-gnu
# - runner: ubuntu-latest
# target: powerpc64le-unknown-linux-gnu
- runner: macos-latest
target: aarch64-apple-darwin
# - runner: macos-latest
# target: x86_64-apple-darwin
- runner: windows-latest
target: x86_64-pc-windows-msvc
# - runner: windows-latest
# target: i686-pc-windows-msvc
# - runner: windows-latest
# target: aarch64-pc-windows-msvc
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Set up Environment
shell: bash
run: rustup target add ${{ matrix.platform.target }}
- name: Set up Windows Environment
shell: bash
run: |
cargo install --target-dir=target -v cargo-vcpkg
cargo vcpkg -v build
if: runner.os == 'Windows'
- name: Set up MacOS Environment
run: brew install autoconf automake libtool
if: runner.os == 'macOS'
- name: Build RustPython
run: cargo build --release --target=${{ matrix.platform.target }} --verbose --features=threading ${{ env.CARGO_ARGS }}
if: runner.os == 'macOS'
- name: Build RustPython
run: cargo build --release --target=${{ matrix.platform.target }} --verbose --features=threading ${{ env.CARGO_ARGS }},jit
if: runner.os != 'macOS'
- name: Rename Binary
run: cp target/${{ matrix.platform.target }}/release/rustpython target/rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}
if: runner.os != 'Windows'
- name: Rename Binary
run: cp target/${{ matrix.platform.target }}/release/rustpython.exe target/rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}.exe
if: runner.os == 'Windows'
- name: Upload Binary Artifacts
uses: actions/upload-artifact@v4
with:
name: rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}
path: target/rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}*
build-wasm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Set up Environment
shell: bash
run: rustup target add wasm32-wasip1
- name: Build RustPython
run: cargo build --target wasm32-wasip1 --no-default-features --features freeze-stdlib,stdlib --release
- name: Rename Binary
run: cp target/wasm32-wasip1/release/rustpython.wasm target/rustpython-release-wasm32-wasip1.wasm
- name: Upload Binary Artifacts
uses: actions/upload-artifact@v4
with:
name: rustpython-release-wasm32-wasip1
path: target/rustpython-release-wasm32-wasip1.wasm
release:
runs-on: ubuntu-latest
needs: [build, build-wasm]
steps:
- name: Download Binary Artifacts
uses: actions/download-artifact@v4
with:
path: bin
pattern: rustpython-release-*
merge-multiple: true
- name: List Binaries
run: |
ls -lah bin/
file bin/*
- name: Create Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref_name }}
run: ${{ github.run_number }}
run: |
if [[ "${{ github.event.inputs.pre-release }}" == "false" ]]; then
RELEASE_TYPE_NAME=Release
PRERELEASE_ARG=
else
RELEASE_TYPE_NAME=Pre-Release
PRERELEASE_ARG=--prerelease
fi
today=$(date '+%Y-%m-%d')
gh release create "$today-$tag-$run" \
--repo="$GITHUB_REPOSITORY" \
--title="RustPython $RELEASE_TYPE_NAME $today-$tag #$run" \
--target="$tag" \
--generate-notes \
$PRERELEASE_ARG \
bin/rustpython-release-*

43
Cargo.lock generated
View File

@@ -207,11 +207,10 @@ dependencies = [
[[package]]
name = "caseless"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808dab3318747be122cb31d36de18d4d1c81277a76f8332a02b81a3d73463d7f"
checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8"
dependencies = [
"regex",
"unicode-normalization",
]
@@ -1566,13 +1565,13 @@ dependencies = [
[[package]]
name = "pmutil"
version = "0.5.3"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004"
checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.77",
]
[[package]]
@@ -1824,24 +1823,23 @@ dependencies = [
[[package]]
name = "result-like"
version = "0.4.6"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc7ce6435c33898517a30e85578cd204cbb696875efb93dec19a2d31294f810"
checksum = "abf7172fef6a7d056b5c26bf6c826570267562d51697f4982ff3ba4aec68a9df"
dependencies = [
"result-like-derive",
]
[[package]]
name = "result-like-derive"
version = "0.4.6"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fabf0a2e54f711c68c50d49f648a1a8a37adcb57353f518ac4df374f0788f42"
checksum = "a8d6574c02e894d66370cfc681e5d68fedbc9a548fb55b30a96b3f0ae22d0fe5"
dependencies = [
"pmutil",
"proc-macro2",
"quote",
"syn 1.0.109",
"syn-ext",
"syn 2.0.77",
]
[[package]]
@@ -2948,9 +2946,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.93"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
dependencies = [
"cfg-if",
"once_cell",
@@ -2959,13 +2957,12 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.93"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.77",
@@ -2986,9 +2983,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.93"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2996,9 +2993,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.93"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [
"proc-macro2",
"quote",
@@ -3009,9 +3006,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.93"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]]
name = "web-sys"

View File

@@ -104,7 +104,7 @@ members = [
version = "0.4.0"
authors = ["RustPython Team"]
edition = "2021"
rust-version = "1.80.0"
rust-version = "1.83.0"
repository = "https://github.com/RustPython/RustPython"
license = "MIT"

22
Lib/_dummy_os.py vendored
View File

@@ -5,22 +5,30 @@ A shim of the os module containing only simple path-related utilities
try:
from os import *
except ImportError:
import abc
import abc, sys
def __getattr__(name):
raise OSError("no os specific module found")
if name in {"_path_normpath", "__path__"}:
raise AttributeError(name)
if name.isupper():
return 0
def dummy(*args, **kwargs):
import io
return io.UnsupportedOperation(f"{name}: no os specific module found")
dummy.__name__ = f"dummy_{name}"
return dummy
def _shim():
import _dummy_os, sys
sys.modules['os'] = _dummy_os
sys.modules['os.path'] = _dummy_os.path
sys.modules['os'] = sys.modules['posix'] = sys.modules[__name__]
import posixpath as path
import sys
sys.modules['os.path'] = path
del sys
sep = path.sep
supports_dir_fd = set()
supports_effective_ids = set()
supports_fd = set()
supports_follow_symlinks = set()
def fspath(path):

35
Lib/contextlib.py vendored
View File

@@ -145,14 +145,17 @@ class _GeneratorContextManager(
except StopIteration:
return False
else:
raise RuntimeError("generator didn't stop")
try:
raise RuntimeError("generator didn't stop")
finally:
self.gen.close()
else:
if value is None:
# Need to force instantiation so we can reliably
# tell if we get the same exception back
value = typ()
try:
self.gen.throw(typ, value, traceback)
self.gen.throw(value)
except StopIteration as exc:
# Suppress StopIteration *unless* it's the same exception that
# was passed to throw(). This prevents a StopIteration
@@ -187,7 +190,10 @@ class _GeneratorContextManager(
raise
exc.__traceback__ = traceback
return False
raise RuntimeError("generator didn't stop after throw()")
try:
raise RuntimeError("generator didn't stop after throw()")
finally:
self.gen.close()
class _AsyncGeneratorContextManager(
_GeneratorContextManagerBase,
@@ -212,14 +218,17 @@ class _AsyncGeneratorContextManager(
except StopAsyncIteration:
return False
else:
raise RuntimeError("generator didn't stop")
try:
raise RuntimeError("generator didn't stop")
finally:
await self.gen.aclose()
else:
if value is None:
# Need to force instantiation so we can reliably
# tell if we get the same exception back
value = typ()
try:
await self.gen.athrow(typ, value, traceback)
await self.gen.athrow(value)
except StopAsyncIteration as exc:
# Suppress StopIteration *unless* it's the same exception that
# was passed to throw(). This prevents a StopIteration
@@ -254,7 +263,10 @@ class _AsyncGeneratorContextManager(
raise
exc.__traceback__ = traceback
return False
raise RuntimeError("generator didn't stop after athrow()")
try:
raise RuntimeError("generator didn't stop after athrow()")
finally:
await self.gen.aclose()
def contextmanager(func):
@@ -441,7 +453,16 @@ class suppress(AbstractContextManager):
# exactly reproduce the limitations of the CPython interpreter.
#
# See http://bugs.python.org/issue12029 for more details
return exctype is not None and issubclass(exctype, self._exceptions)
if exctype is None:
return
if issubclass(exctype, self._exceptions):
return True
if issubclass(exctype, BaseExceptionGroup):
match, rest = excinst.split(self._exceptions)
if rest is None:
return True
raise rest
return False
class _BaseExitStack:

12
Lib/io.py vendored
View File

@@ -55,10 +55,15 @@ import _io
import abc
from _io import (DEFAULT_BUFFER_SIZE, BlockingIOError, UnsupportedOperation,
open, open_code, FileIO, BytesIO, StringIO, BufferedReader,
open, open_code, BytesIO, StringIO, BufferedReader,
BufferedWriter, BufferedRWPair, BufferedRandom,
IncrementalNewlineDecoder, text_encoding, TextIOWrapper)
try:
from _io import FileIO
except ImportError:
pass
# Pretend this exception was created here.
UnsupportedOperation.__module__ = "io"
@@ -82,7 +87,10 @@ class BufferedIOBase(_io._BufferedIOBase, IOBase):
class TextIOBase(_io._TextIOBase, IOBase):
__doc__ = _io._TextIOBase.__doc__
RawIOBase.register(FileIO)
try:
RawIOBase.register(FileIO)
except NameError:
pass
for klass in (BytesIO, BufferedReader, BufferedWriter, BufferedRandom,
BufferedRWPair):

65
Lib/test/support/testcase.py vendored Normal file
View File

@@ -0,0 +1,65 @@
from math import copysign, isnan
class ExceptionIsLikeMixin:
def assertExceptionIsLike(self, exc, template):
"""
Passes when the provided `exc` matches the structure of `template`.
Individual exceptions don't have to be the same objects or even pass
an equality test: they only need to be the same type and contain equal
`exc_obj.args`.
"""
if exc is None and template is None:
return
if template is None:
self.fail(f"unexpected exception: {exc}")
if exc is None:
self.fail(f"expected an exception like {template!r}, got None")
if not isinstance(exc, ExceptionGroup):
self.assertEqual(exc.__class__, template.__class__)
self.assertEqual(exc.args[0], template.args[0])
else:
self.assertEqual(exc.message, template.message)
self.assertEqual(len(exc.exceptions), len(template.exceptions))
for e, t in zip(exc.exceptions, template.exceptions):
self.assertExceptionIsLike(e, t)
class FloatsAreIdenticalMixin:
def assertFloatsAreIdentical(self, x, y):
"""Fail unless floats x and y are identical, in the sense that:
(1) both x and y are nans, or
(2) both x and y are infinities, with the same sign, or
(3) both x and y are zeros, with the same sign, or
(4) x and y are both finite and nonzero, and x == y
"""
msg = 'floats {!r} and {!r} are not identical'
if isnan(x) or isnan(y):
if isnan(x) and isnan(y):
return
elif x == y:
if x != 0.0:
return
# both zero; check that signs match
elif copysign(1.0, x) == copysign(1.0, y):
return
else:
msg += ': zeros have different signs'
self.fail(msg.format(x, y))
class ComplexesAreIdenticalMixin(FloatsAreIdenticalMixin):
def assertComplexesAreIdentical(self, x, y):
"""Fail unless complex numbers x and y have equal values and signs.
In particular, if x and y both have real (or imaginary) part
zero, but the zeros have different signs, this test will fail.
"""
self.assertFloatsAreIdentical(x.real, y.real)
self.assertFloatsAreIdentical(x.imag, y.imag)

View File

@@ -10,6 +10,7 @@ import unittest
from contextlib import * # Tests __all__
from test import support
from test.support import os_helper
from test.support.testcase import ExceptionIsLikeMixin
import weakref
@@ -158,9 +159,45 @@ class ContextManagerTestCase(unittest.TestCase):
yield
ctx = whoo()
ctx.__enter__()
self.assertRaises(
RuntimeError, ctx.__exit__, TypeError, TypeError("foo"), None
)
with self.assertRaises(RuntimeError):
ctx.__exit__(TypeError, TypeError("foo"), None)
if support.check_impl_detail(cpython=True):
# The "gen" attribute is an implementation detail.
self.assertFalse(ctx.gen.gi_suspended)
def test_contextmanager_trap_no_yield(self):
@contextmanager
def whoo():
if False:
yield
ctx = whoo()
with self.assertRaises(RuntimeError):
ctx.__enter__()
def test_contextmanager_trap_second_yield(self):
@contextmanager
def whoo():
yield
yield
ctx = whoo()
ctx.__enter__()
with self.assertRaises(RuntimeError):
ctx.__exit__(None, None, None)
if support.check_impl_detail(cpython=True):
# The "gen" attribute is an implementation detail.
self.assertFalse(ctx.gen.gi_suspended)
def test_contextmanager_non_normalised(self):
@contextmanager
def whoo():
try:
yield
except RuntimeError:
raise SyntaxError
ctx = whoo()
ctx.__enter__()
with self.assertRaises(SyntaxError):
ctx.__exit__(RuntimeError, None, None)
def test_contextmanager_except(self):
state = []
@@ -241,6 +278,23 @@ def woohoo():
self.assertEqual(ex.args[0], 'issue29692:Unchained')
self.assertIsNone(ex.__cause__)
def test_contextmanager_wrap_runtimeerror(self):
@contextmanager
def woohoo():
try:
yield
except Exception as exc:
raise RuntimeError(f'caught {exc}') from exc
with self.assertRaises(RuntimeError):
with woohoo():
1 / 0
# If the context manager wrapped StopIteration in a RuntimeError,
# we also unwrap it, because we can't tell whether the wrapping was
# done by the generator machinery or by the generator itself.
with self.assertRaises(StopIteration):
with woohoo():
raise StopIteration
def _create_contextmanager_attribs(self):
def attribs(**kw):
def decorate(func):
@@ -252,6 +306,7 @@ def woohoo():
@attribs(foo='bar')
def baz(spam):
"""Whee!"""
yield
return baz
def test_contextmanager_attribs(self):
@@ -308,8 +363,11 @@ def woohoo():
def test_recursive(self):
depth = 0
ncols = 0
@contextmanager
def woohoo():
nonlocal ncols
ncols += 1
nonlocal depth
before = depth
depth += 1
@@ -323,6 +381,7 @@ def woohoo():
recursive()
recursive()
self.assertEqual(ncols, 10)
self.assertEqual(depth, 0)
@@ -374,12 +433,10 @@ class FileContextTestCase(unittest.TestCase):
def testWithOpen(self):
tfn = tempfile.mktemp()
try:
f = None
with open(tfn, "w", encoding="utf-8") as f:
self.assertFalse(f.closed)
f.write("Booh\n")
self.assertTrue(f.closed)
f = None
with self.assertRaises(ZeroDivisionError):
with open(tfn, "r", encoding="utf-8") as f:
self.assertFalse(f.closed)
@@ -1160,7 +1217,7 @@ class TestRedirectStderr(TestRedirectStream, unittest.TestCase):
orig_stream = "stderr"
class TestSuppress(unittest.TestCase):
class TestSuppress(ExceptionIsLikeMixin, unittest.TestCase):
@support.requires_docstrings
def test_instance_docs(self):
@@ -1214,6 +1271,51 @@ class TestSuppress(unittest.TestCase):
1/0
self.assertTrue(outer_continued)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_exception_groups(self):
eg_ve = lambda: ExceptionGroup(
"EG with ValueErrors only",
[ValueError("ve1"), ValueError("ve2"), ValueError("ve3")],
)
eg_all = lambda: ExceptionGroup(
"EG with many types of exceptions",
[ValueError("ve1"), KeyError("ke1"), ValueError("ve2"), KeyError("ke2")],
)
with suppress(ValueError):
raise eg_ve()
with suppress(ValueError, KeyError):
raise eg_all()
with self.assertRaises(ExceptionGroup) as eg1:
with suppress(ValueError):
raise eg_all()
self.assertExceptionIsLike(
eg1.exception,
ExceptionGroup(
"EG with many types of exceptions",
[KeyError("ke1"), KeyError("ke2")],
),
)
# Check handling of BaseExceptionGroup, using GeneratorExit so that
# we don't accidentally discard a ctrl-c with KeyboardInterrupt.
with suppress(GeneratorExit):
raise BaseExceptionGroup("message", [GeneratorExit()])
# If we raise a BaseException group, we can still suppress parts
with self.assertRaises(BaseExceptionGroup) as eg1:
with suppress(KeyError):
raise BaseExceptionGroup("message", [GeneratorExit("g"), KeyError("k")])
self.assertExceptionIsLike(
eg1.exception, BaseExceptionGroup("message", [GeneratorExit("g")]),
)
# If we suppress all the leaf BaseExceptions, we get a non-base ExceptionGroup
with self.assertRaises(ExceptionGroup) as eg1:
with suppress(GeneratorExit):
raise BaseExceptionGroup("message", [GeneratorExit("g"), KeyError("k")])
self.assertExceptionIsLike(
eg1.exception, ExceptionGroup("message", [KeyError("k")]),
)
class TestChdir(unittest.TestCase):
def make_relative_path(self, *parts):

102
Lib/test/test_float.py vendored
View File

@@ -8,7 +8,7 @@ import time
import unittest
from test import support
from test.support import import_helper
from test.support.testcase import FloatsAreIdenticalMixin
from test.test_grammar import (VALID_UNDERSCORE_LITERALS,
INVALID_UNDERSCORE_LITERALS)
from math import isinf, isnan, copysign, ldexp
@@ -19,7 +19,6 @@ try:
except ImportError:
_testcapi = None
HAVE_IEEE_754 = float.__getformat__("double").startswith("IEEE")
INF = float("inf")
NAN = float("nan")
@@ -742,8 +741,13 @@ class FormatTestCase(unittest.TestCase):
lhs, rhs = map(str.strip, line.split('->'))
fmt, arg = lhs.split()
self.assertEqual(fmt % float(arg), rhs)
self.assertEqual(fmt % -float(arg), '-' + rhs)
f = float(arg)
self.assertEqual(fmt % f, rhs)
self.assertEqual(fmt % -f, '-' + rhs)
if fmt != '%r':
fmt2 = fmt[1:]
self.assertEqual(format(f, fmt2), rhs)
self.assertEqual(format(-f, fmt2), '-' + rhs)
def test_issue5864(self):
self.assertEqual(format(123.456, '.4'), '123.5')
@@ -833,7 +837,7 @@ class ReprTestCase(unittest.TestCase):
self.assertEqual(repr(float(negs)), str(float(negs)))
@support.requires_IEEE_754
class RoundTestCase(unittest.TestCase):
class RoundTestCase(unittest.TestCase, FloatsAreIdenticalMixin):
def test_inf_nan(self):
self.assertRaises(OverflowError, round, INF)
@@ -863,10 +867,10 @@ class RoundTestCase(unittest.TestCase):
def test_small_n(self):
for n in [-308, -309, -400, 1-2**31, -2**31, -2**31-1, -2**100]:
self.assertEqual(round(123.456, n), 0.0)
self.assertEqual(round(-123.456, n), -0.0)
self.assertEqual(round(1e300, n), 0.0)
self.assertEqual(round(1e-320, n), 0.0)
self.assertFloatsAreIdentical(round(123.456, n), 0.0)
self.assertFloatsAreIdentical(round(-123.456, n), -0.0)
self.assertFloatsAreIdentical(round(1e300, n), 0.0)
self.assertFloatsAreIdentical(round(1e-320, n), 0.0)
def test_overflow(self):
self.assertRaises(OverflowError, round, 1.6e308, -308)
@@ -1053,32 +1057,22 @@ class InfNanTest(unittest.TestCase):
self.assertEqual(copysign(1.0, float('inf')), 1.0)
self.assertEqual(copysign(1.0, float('-inf')), -1.0)
@unittest.skipUnless(getattr(sys, 'float_repr_style', '') == 'short',
"applies only when using short float repr style")
def test_nan_signs(self):
# When using the dtoa.c code, the sign of float('nan') should
# be predictable.
# The sign of float('nan') should be predictable.
self.assertEqual(copysign(1.0, float('nan')), 1.0)
self.assertEqual(copysign(1.0, float('-nan')), -1.0)
fromHex = float.fromhex
toHex = float.hex
class HexFloatTestCase(unittest.TestCase):
class HexFloatTestCase(FloatsAreIdenticalMixin, unittest.TestCase):
MAX = fromHex('0x.fffffffffffff8p+1024') # max normal
MIN = fromHex('0x1p-1022') # min normal
TINY = fromHex('0x0.0000000000001p-1022') # min subnormal
EPS = fromHex('0x0.0000000000001p0') # diff between 1.0 and next float up
def identical(self, x, y):
# check that floats x and y are identical, or that both
# are NaNs
if isnan(x) or isnan(y):
if isnan(x) == isnan(y):
return
elif x == y and (x != 0.0 or copysign(1.0, x) == copysign(1.0, y)):
return
self.fail('%r not identical to %r' % (x, y))
self.assertFloatsAreIdentical(x, y)
def test_ends(self):
self.identical(self.MIN, ldexp(1.0, -1022))
@@ -1517,69 +1511,5 @@ class HexFloatTestCase(unittest.TestCase):
self.assertEqual(getattr(f, 'foo', 'none'), 'bar')
# Test PyFloat_Pack2(), PyFloat_Pack4() and PyFloat_Pack8()
# Test PyFloat_Unpack2(), PyFloat_Unpack4() and PyFloat_Unpack8()
BIG_ENDIAN = 0
LITTLE_ENDIAN = 1
EPSILON = {
2: 2.0 ** -11, # binary16
4: 2.0 ** -24, # binary32
8: 2.0 ** -53, # binary64
}
@unittest.skipIf(_testcapi is None, 'needs _testcapi')
class PackTests(unittest.TestCase):
def test_pack(self):
self.assertEqual(_testcapi.float_pack(2, 1.5, BIG_ENDIAN),
b'>\x00')
self.assertEqual(_testcapi.float_pack(4, 1.5, BIG_ENDIAN),
b'?\xc0\x00\x00')
self.assertEqual(_testcapi.float_pack(8, 1.5, BIG_ENDIAN),
b'?\xf8\x00\x00\x00\x00\x00\x00')
self.assertEqual(_testcapi.float_pack(2, 1.5, LITTLE_ENDIAN),
b'\x00>')
self.assertEqual(_testcapi.float_pack(4, 1.5, LITTLE_ENDIAN),
b'\x00\x00\xc0?')
self.assertEqual(_testcapi.float_pack(8, 1.5, LITTLE_ENDIAN),
b'\x00\x00\x00\x00\x00\x00\xf8?')
def test_unpack(self):
self.assertEqual(_testcapi.float_unpack(b'>\x00', BIG_ENDIAN),
1.5)
self.assertEqual(_testcapi.float_unpack(b'?\xc0\x00\x00', BIG_ENDIAN),
1.5)
self.assertEqual(_testcapi.float_unpack(b'?\xf8\x00\x00\x00\x00\x00\x00', BIG_ENDIAN),
1.5)
self.assertEqual(_testcapi.float_unpack(b'\x00>', LITTLE_ENDIAN),
1.5)
self.assertEqual(_testcapi.float_unpack(b'\x00\x00\xc0?', LITTLE_ENDIAN),
1.5)
self.assertEqual(_testcapi.float_unpack(b'\x00\x00\x00\x00\x00\x00\xf8?', LITTLE_ENDIAN),
1.5)
def test_roundtrip(self):
large = 2.0 ** 100
values = [1.0, 1.5, large, 1.0/7, math.pi]
if HAVE_IEEE_754:
values.extend((INF, NAN))
for value in values:
for size in (2, 4, 8,):
if size == 2 and value == large:
# too large for 16-bit float
continue
rel_tol = EPSILON[size]
for endian in (BIG_ENDIAN, LITTLE_ENDIAN):
with self.subTest(value=value, size=size, endian=endian):
data = _testcapi.float_pack(size, value, endian)
value2 = _testcapi.float_unpack(data, endian)
if isnan(value):
self.assertTrue(isnan(value2), (value, value2))
elif size < 8:
self.assertTrue(math.isclose(value2, value, rel_tol=rel_tol),
(value, value2))
else:
self.assertEqual(value2, value)
if __name__ == '__main__':
unittest.main()

1748
Lib/test/test_fstring.py vendored

File diff suppressed because it is too large Load Diff

View File

@@ -56,7 +56,7 @@ NOTE: For windows users, please set `RUSTPYTHONPATH` environment variable as `Li
You can also install and run RustPython with the following:
```bash
$ cargo install --git https://github.com/RustPython/RustPython
$ cargo install --git https://github.com/RustPython/RustPython rustpython
$ rustpython
Welcome to the magnificent Rust Python interpreter
>>>>>
@@ -91,13 +91,13 @@ You can compile RustPython to a standalone WebAssembly WASI module so it can run
Build
```bash
cargo build --target wasm32-wasi --no-default-features --features freeze-stdlib,stdlib --release
cargo build --target wasm32-wasip1 --no-default-features --features freeze-stdlib,stdlib --release
```
Run by wasmer
```bash
wasmer run --dir `pwd` -- target/wasm32-wasi/release/rustpython.wasm `pwd`/extra_tests/snippets/stdlib_random.py
wasmer run --dir `pwd` -- target/wasm32-wasip1/release/rustpython.wasm `pwd`/extra_tests/snippets/stdlib_random.py
```
Run by wapm
@@ -114,10 +114,10 @@ $ wapm run rustpython
You can build the WebAssembly WASI file with:
```bash
cargo build --release --target wasm32-wasi --features="freeze-stdlib"
cargo build --release --target wasm32-wasip1 --features="freeze-stdlib"
```
> Note: we use the `freeze-stdlib` to include the standard library inside the binary. You also have to run once `rustup target add wasm32-wasi`.
> Note: we use the `freeze-stdlib` to include the standard library inside the binary. You also have to run once `rustup target add wasm32-wasip1`.
### JIT (Just in time) compiler

View File

@@ -1,7 +1,8 @@
//! An unresizable vector backed by a `Box<[T]>`
#![allow(clippy::needless_lifetimes)]
use std::{
alloc,
borrow::{Borrow, BorrowMut},
cmp, fmt,
mem::{self, MaybeUninit},
@@ -35,29 +36,11 @@ macro_rules! panic_oob {
};
}
fn capacity_overflow() -> ! {
panic!("capacity overflow")
}
impl<T> BoxVec<T> {
pub fn new(n: usize) -> BoxVec<T> {
unsafe {
let layout = match alloc::Layout::array::<T>(n) {
Ok(l) => l,
Err(_) => capacity_overflow(),
};
let ptr = if mem::size_of::<T>() == 0 {
ptr::NonNull::<MaybeUninit<T>>::dangling().as_ptr()
} else {
let ptr = alloc::alloc(layout);
if ptr.is_null() {
alloc::handle_alloc_error(layout)
}
ptr as *mut MaybeUninit<T>
};
let ptr = ptr::slice_from_raw_parts_mut(ptr, n);
let xs = Box::from_raw(ptr);
BoxVec { xs, len: 0 }
BoxVec {
xs: Box::new_uninit_slice(n),
len: 0,
}
}

View File

@@ -60,9 +60,9 @@ pub fn bytes_to_int(lit: &[u8], mut base: u32) -> Option<BigInt> {
return Some(BigInt::zero());
}
}
16 => lit.get(1).map_or(false, |&b| matches!(b, b'x' | b'X')),
2 => lit.get(1).map_or(false, |&b| matches!(b, b'b' | b'B')),
8 => lit.get(1).map_or(false, |&b| matches!(b, b'o' | b'O')),
16 => lit.get(1).is_some_and(|&b| matches!(b, b'x' | b'X')),
2 => lit.get(1).is_some_and(|&b| matches!(b, b'b' | b'B')),
8 => lit.get(1).is_some_and(|&b| matches!(b, b'o' | b'O')),
_ => false,
}
} else {

View File

@@ -1,6 +1,6 @@
//! A crate to hold types and functions common to all rustpython components.
#![cfg_attr(target_os = "redox", feature(byte_slice_trim_ascii))]
#![cfg_attr(target_os = "redox", feature(byte_slice_trim_ascii, new_uninit))]
#[macro_use]
mod macros;

View File

@@ -2,7 +2,7 @@ use lock_api::{
GetThreadId, RawMutex, RawRwLock, RawRwLockDowngrade, RawRwLockRecursive, RawRwLockUpgrade,
RawRwLockUpgradeDowngrade,
};
use std::{cell::Cell, num::NonZeroUsize};
use std::{cell::Cell, num::NonZero};
pub struct RawCellMutex {
locked: Cell<bool>,
@@ -203,7 +203,7 @@ fn deadlock(lock_kind: &str, ty: &str) -> ! {
pub struct SingleThreadId(());
unsafe impl GetThreadId for SingleThreadId {
const INIT: Self = SingleThreadId(());
fn nonzero_thread_id(&self) -> NonZeroUsize {
NonZeroUsize::new(1).unwrap()
fn nonzero_thread_id(&self) -> NonZero<usize> {
NonZero::new(1).unwrap()
}
}

View File

@@ -1,3 +1,5 @@
#![allow(clippy::needless_lifetimes)]
use lock_api::{MutexGuard, RawMutex};
use std::{fmt, marker::PhantomData, ops::Deref};

View File

@@ -1,3 +1,5 @@
#![allow(clippy::needless_lifetimes)]
use lock_api::{GetThreadId, GuardNoSend, RawMutex};
use std::{
cell::UnsafeCell,

View File

@@ -331,12 +331,13 @@ pub mod levenshtein {
/// ```
#[macro_export]
macro_rules! ascii {
($x:literal) => {{
const STR: &str = $x;
const _: () = if !STR.is_ascii() {
panic!("ascii!() argument is not an ascii string");
($x:expr $(,)?) => {{
let s = const {
let s: &str = $x;
assert!(s.is_ascii(), "ascii!() argument is not an ascii string");
s
};
unsafe { $crate::vendored::ascii::AsciiStr::from_ascii_unchecked(STR.as_bytes()) }
unsafe { $crate::vendored::ascii::AsciiStr::from_ascii_unchecked(s.as_bytes()) }
}};
}
pub use ascii;

View File

@@ -3102,19 +3102,18 @@ impl Compiler {
Expr::Tuple(ExprTuple { elts, .. }) => elts.iter().any(Self::contains_await),
Expr::Set(ExprSet { elts, .. }) => elts.iter().any(Self::contains_await),
Expr::Dict(ExprDict { keys, values, .. }) => {
keys.iter()
.any(|key| key.as_ref().map_or(false, Self::contains_await))
keys.iter().flatten().any(Self::contains_await)
|| values.iter().any(Self::contains_await)
}
Expr::Slice(ExprSlice {
lower, upper, step, ..
}) => {
lower.as_ref().map_or(false, |l| Self::contains_await(l))
|| upper.as_ref().map_or(false, |u| Self::contains_await(u))
|| step.as_ref().map_or(false, |s| Self::contains_await(s))
lower.as_deref().is_some_and(Self::contains_await)
|| upper.as_deref().is_some_and(Self::contains_await)
|| step.as_deref().is_some_and(Self::contains_await)
}
Expr::Yield(ExprYield { value, .. }) => {
value.as_ref().map_or(false, |v| Self::contains_await(v))
value.as_deref().is_some_and(Self::contains_await)
}
Expr::Await(ExprAwait { .. }) => true,
Expr::YieldFrom(ExprYieldFrom { value, .. }) => Self::contains_await(value),
@@ -3128,9 +3127,7 @@ impl Compiler {
..
}) => {
Self::contains_await(value)
|| format_spec
.as_ref()
.map_or(false, |fs| Self::contains_await(fs))
|| format_spec.as_deref().is_some_and(Self::contains_await)
}
Expr::Name(located_ast::ExprName { .. }) => false,
Expr::Lambda(located_ast::ExprLambda { body, .. }) => Self::contains_await(body),

View File

@@ -102,8 +102,7 @@ impl FrozenLib<Vec<u8>> {
/// Encode the given iterator of frozen modules into a compressed vector of bytes
pub fn encode<'a, I, B: AsRef<[u8]>>(lib: I) -> FrozenLib<Vec<u8>>
where
I: IntoIterator<Item = (&'a str, FrozenModule<B>)>,
I::IntoIter: ExactSizeIterator + Clone,
I: IntoIterator<Item = (&'a str, FrozenModule<B>), IntoIter: ExactSizeIterator + Clone>,
{
let iter = lib.into_iter();
let mut bytes = Vec::new();

View File

@@ -348,7 +348,7 @@ fn generate_class_def(
&& if let Ok(Meta::List(l)) = attr.parse_meta() {
l.nested
.into_iter()
.any(|n| n.get_ident().map_or(false, |p| p == "PyStructSequence"))
.any(|n| n.get_ident().is_some_and(|p| p == "PyStructSequence"))
} else {
false
}

View File

@@ -558,7 +558,7 @@ impl AttributeExt for Attribute {
let has_name = list
.nested
.iter()
.any(|nested_meta| nested_meta.get_path().map_or(false, |p| p.is_ident(name)));
.any(|nested_meta| nested_meta.get_path().is_some_and(|p| p.is_ident(name)));
if !has_name {
list.nested.push(new_item())
}

View File

@@ -32,3 +32,23 @@ assert_raises(ValueError, lambda: 1 << -1)
# Right shift raises value error on negative
assert_raises(ValueError, lambda: 1 >> -1)
# Bitwise or, and, xor raises value error on incompatible types
assert_raises(TypeError, lambda: "abc" | True)
assert_raises(TypeError, lambda: "abc" & True)
assert_raises(TypeError, lambda: "abc" ^ True)
assert_raises(TypeError, lambda: True | "abc")
assert_raises(TypeError, lambda: True & "abc")
assert_raises(TypeError, lambda: True ^ "abc")
assert_raises(TypeError, lambda: "abc" | 1.5)
assert_raises(TypeError, lambda: "abc" & 1.5)
assert_raises(TypeError, lambda: "abc" ^ 1.5)
assert_raises(TypeError, lambda: 1.5 | "abc")
assert_raises(TypeError, lambda: 1.5 & "abc")
assert_raises(TypeError, lambda: 1.5 ^ "abc")
assert_raises(TypeError, lambda: True | 1.5)
assert_raises(TypeError, lambda: True & 1.5)
assert_raises(TypeError, lambda: True ^ 1.5)
assert_raises(TypeError, lambda: 1.5 | True)
assert_raises(TypeError, lambda: 1.5 & True)
assert_raises(TypeError, lambda: 1.5 ^ True)

View File

@@ -502,9 +502,9 @@ with TestWithTempDir() as tmpdir:
assert set(collected_files) == set(expected_files)
# system()
if "win" not in sys.platform:
assert os.system('ls') == 0
assert os.system('{') != 0
if os.name in ('posix', 'nt'):
assert os.system('echo test') == 0
assert os.system('&') != 0
for arg in [None, 1, 1.0, TabError]:
assert_raises(TypeError, os.system, arg)

View File

@@ -1,3 +1,5 @@
use super::{JitCompileError, JitSig, JitType};
use cranelift::codegen::ir::FuncRef;
use cranelift::prelude::*;
use num_traits::cast::ToPrimitive;
use rustpython_compiler_core::bytecode::{
@@ -6,8 +8,6 @@ use rustpython_compiler_core::bytecode::{
};
use std::collections::HashMap;
use super::{JitCompileError, JitSig, JitType};
#[repr(u16)]
enum CustomTrapCode {
/// Raised when shifting by a negative number
@@ -27,6 +27,7 @@ enum JitValue {
Bool(Value),
None,
Tuple(Vec<JitValue>),
FuncRef(FuncRef),
}
impl JitValue {
@@ -43,14 +44,14 @@ impl JitValue {
JitValue::Int(_) => Some(JitType::Int),
JitValue::Float(_) => Some(JitType::Float),
JitValue::Bool(_) => Some(JitType::Bool),
JitValue::None | JitValue::Tuple(_) => None,
JitValue::None | JitValue::Tuple(_) | JitValue::FuncRef(_) => None,
}
}
fn into_value(self) -> Option<Value> {
match self {
JitValue::Int(val) | JitValue::Float(val) | JitValue::Bool(val) => Some(val),
JitValue::None | JitValue::Tuple(_) => None,
JitValue::None | JitValue::Tuple(_) | JitValue::FuncRef(_) => None,
}
}
}
@@ -68,6 +69,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
builder: &'a mut FunctionBuilder<'b>,
num_variables: usize,
arg_types: &[JitType],
ret_type: Option<JitType>,
entry_block: Block,
) -> FunctionCompiler<'a, 'b> {
let mut compiler = FunctionCompiler {
@@ -77,7 +79,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
label_to_block: HashMap::new(),
sig: JitSig {
args: arg_types.to_vec(),
ret: None,
ret: ret_type,
},
};
let params = compiler.builder.func.dfg.block_params(entry_block).to_vec();
@@ -132,7 +134,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
}
JitValue::Bool(val) => Ok(val),
JitValue::None => Ok(self.builder.ins().iconst(types::I8, 0)),
JitValue::Tuple(_) => Err(JitCompileError::NotSupported),
JitValue::Tuple(_) | JitValue::FuncRef(_) => Err(JitCompileError::NotSupported),
}
}
@@ -146,6 +148,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
pub fn compile<C: bytecode::Constant>(
&mut self,
func_ref: FuncRef,
bytecode: &CodeObject<C>,
) -> Result<(), JitCompileError> {
// TODO: figure out if this is sufficient -- previously individual labels were associated
@@ -177,7 +180,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
continue;
}
self.add_instruction(instruction, arg, &bytecode.constants)?;
self.add_instruction(func_ref, bytecode, instruction, arg)?;
}
Ok(())
@@ -229,9 +232,10 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
pub fn add_instruction<C: bytecode::Constant>(
&mut self,
func_ref: FuncRef,
bytecode: &CodeObject<C>,
instruction: Instruction,
arg: OpArg,
constants: &[C],
) -> Result<(), JitCompileError> {
match instruction {
Instruction::ExtendedArg => Ok(()),
@@ -282,7 +286,8 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
self.store_variable(idx.get(arg), val)
}
Instruction::LoadConst { idx } => {
let val = self.prepare_const(constants[idx.get(arg) as usize].borrow_constant())?;
let val = self
.prepare_const(bytecode.constants[idx.get(arg) as usize].borrow_constant())?;
self.stack.push(val);
Ok(())
}
@@ -311,7 +316,8 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
self.return_value(val)
}
Instruction::ReturnConst { idx } => {
let val = self.prepare_const(constants[idx.get(arg) as usize].borrow_constant())?;
let val = self
.prepare_const(bytecode.constants[idx.get(arg) as usize].borrow_constant())?;
self.return_value(val)
}
Instruction::CompareOperation { op, .. } => {
@@ -508,6 +514,36 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
// TODO: block support
Ok(())
}
Instruction::LoadGlobal(idx) => {
let name = &bytecode.names[idx.get(arg) as usize];
if name.as_ref() != bytecode.obj_name.as_ref() {
Err(JitCompileError::NotSupported)
} else {
self.stack.push(JitValue::FuncRef(func_ref));
Ok(())
}
}
Instruction::CallFunctionPositional { nargs } => {
let nargs = nargs.get(arg);
let mut args = Vec::new();
for _ in 0..nargs {
let arg = self.stack.pop().ok_or(JitCompileError::BadBytecode)?;
args.push(arg.into_value().unwrap());
}
match self.stack.pop().ok_or(JitCompileError::BadBytecode)? {
JitValue::FuncRef(reference) => {
let call = self.builder.ins().call(reference, &args);
let returns = self.builder.inst_results(call);
self.stack.push(JitValue::Int(returns[0]));
Ok(())
}
_ => Err(JitCompileError::BadBytecode),
}
}
_ => Err(JitCompileError::NotSupported),
}
}

View File

@@ -49,6 +49,7 @@ impl Jit {
&mut self,
bytecode: &bytecode::CodeObject<C>,
args: &[JitType],
ret: Option<JitType>,
) -> Result<(FuncId, JitSig), JitCompileError> {
for arg in args {
self.ctx
@@ -58,22 +59,13 @@ impl Jit {
.push(AbiParam::new(arg.to_cranelift()));
}
let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut self.builder_context);
let entry_block = builder.create_block();
builder.append_block_params_for_function_params(entry_block);
builder.switch_to_block(entry_block);
let sig = {
let mut compiler =
FunctionCompiler::new(&mut builder, bytecode.varnames.len(), args, entry_block);
compiler.compile(bytecode)?;
compiler.sig
};
builder.seal_all_blocks();
builder.finalize();
if ret.is_some() {
self.ctx
.func
.signature
.returns
.push(AbiParam::new(ret.clone().unwrap().to_cranelift()));
}
let id = self.module.declare_function(
&format!("jit_{}", bytecode.obj_name.as_ref()),
@@ -81,6 +73,30 @@ impl Jit {
&self.ctx.func.signature,
)?;
let func_ref = self.module.declare_func_in_func(id, &mut self.ctx.func);
let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut self.builder_context);
let entry_block = builder.create_block();
builder.append_block_params_for_function_params(entry_block);
builder.switch_to_block(entry_block);
let sig = {
let mut compiler = FunctionCompiler::new(
&mut builder,
bytecode.varnames.len(),
args,
ret,
entry_block,
);
compiler.compile(func_ref, bytecode)?;
compiler.sig
};
builder.seal_all_blocks();
builder.finalize();
self.module.define_function(id, &mut self.ctx)?;
self.module.clear_context(&mut self.ctx);
@@ -92,10 +108,11 @@ impl Jit {
pub fn compile<C: bytecode::Constant>(
bytecode: &bytecode::CodeObject<C>,
args: &[JitType],
ret: Option<JitType>,
) -> Result<CompiledCode, JitCompileError> {
let mut jit = Jit::new();
let (id, sig) = jit.build_function(bytecode, args)?;
let (id, sig) = jit.build_function(bytecode, args, ret)?;
jit.module.finalize_definitions();

View File

@@ -27,7 +27,17 @@ impl Function {
arg_types.push(arg_type);
}
rustpython_jit::compile(&self.code, &arg_types).expect("Compile failure")
let ret_type = match self.annotations.get("return") {
Some(StackValue::String(annotation)) => match annotation.as_str() {
"int" => Some(JitType::Int),
"float" => Some(JitType::Float),
"bool" => Some(JitType::Bool),
_ => panic!("Unrecognised jit type"),
},
_ => None,
};
rustpython_jit::compile(&self.code, &arg_types, ret_type).expect("Compile failure")
}
}

View File

@@ -113,3 +113,15 @@ fn test_unpack_tuple() {
assert_eq!(unpack_tuple(0, 1), Ok(1));
assert_eq!(unpack_tuple(1, 2), Ok(2));
}
#[test]
fn test_recursive_fib() {
let fib = jit_function! { fib(n: i64) -> i64 => r##"
def fib(n: int) -> int:
if n == 0 or n == 1:
return 1
return fib(n-1) + fib(n-2)
"## };
assert_eq!(fib(10), Ok(89));
}

View File

@@ -7,6 +7,6 @@ FEATURES_FOR_WAPM=(stdlib zlib)
export BUILDTIME_RUSTPYTHONPATH="/lib/rustpython"
cargo build --release --target wasm32-wasi --no-default-features --features="${FEATURES_FOR_WAPM[*]}"
cargo build --release --target wasm32-wasip1 --no-default-features --features="${FEATURES_FOR_WAPM[*]}"
wapm publish

View File

@@ -58,6 +58,7 @@ use std::process::ExitCode;
pub use interpreter::InterpreterConfig;
pub use rustpython_vm as vm;
pub use settings::{opts_with_clap, InstallPipMode, RunMode};
pub use shell::run_shell;
/// The main cli of the `rustpython` interpreter. This function will return `std::process::ExitCode`
/// based on the return code of the python code ran through the cli.

View File

@@ -22,7 +22,7 @@ fn split_idents_on_dot(line: &str) -> Option<(usize, Vec<String>)> {
match c {
'.' => {
// check for a double dot
if i != 0 && words.last().map_or(false, |s| s.is_empty()) {
if i != 0 && words.last().is_some_and(|s| s.is_empty()) {
return None;
}
reverse_string(words.last_mut().unwrap());

View File

@@ -1238,7 +1238,7 @@ mod array {
let res = match array_a.cmp(&array_b) {
// fast path for same ArrayContentType type
Ok(partial_ord) => partial_ord.map_or(false, |ord| op.eval_ord(ord)),
Ok(partial_ord) => partial_ord.is_some_and(|ord| op.eval_ord(ord)),
Err(()) => {
let iter = Iterator::zip(array_a.iter(vm), array_b.iter(vm));

View File

@@ -129,18 +129,16 @@ mod _contextvars {
super::CONTEXTS.with(|ctxs| {
let mut ctxs = ctxs.borrow_mut();
if !ctxs
.last()
.map_or(false, |ctx| ctx.get_id() == zelf.get_id())
{
// TODO: use Vec::pop_if once stabilized
if ctxs.last().is_some_and(|ctx| ctx.get_id() == zelf.get_id()) {
let _ = ctxs.pop();
Ok(())
} else {
let msg =
"cannot exit context: thread state references a different context object"
.to_owned();
return Err(vm.new_runtime_error(msg));
Err(vm.new_runtime_error(msg))
}
let _ = ctxs.pop();
Ok(())
})?;
zelf.inner.entered.set(false);

View File

@@ -83,7 +83,7 @@ mod grp {
#[pyfunction]
fn getgrall(vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
// setgrent, getgrent, etc are not thread safe. Could use fgetgrent_r, but this is easier
static GETGRALL: parking_lot::Mutex<()> = parking_lot::const_mutex(());
static GETGRALL: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
let _guard = GETGRALL.lock();
let mut list = Vec::new();

View File

@@ -1,6 +1,7 @@
// to allow `mod foo {}` in foo.rs; clippy thinks this is a mistake/misunderstanding of
// how `mod` works, but we want this sometimes for pymodule declarations
#![allow(clippy::module_inception)]
#![cfg_attr(target_os = "redox", feature(raw_ref_op))]
#[macro_use]
extern crate rustpython_derive;

View File

@@ -36,7 +36,7 @@ mod platform {
// based off winsock2.h: https://gist.github.com/piscisaureus/906386#file-winsock2-h-L128-L141
pub unsafe fn FD_SET(fd: RawFd, set: *mut fd_set) {
let mut slot = std::ptr::addr_of_mut!((*set).fd_array).cast::<RawFd>();
let mut slot = (&raw mut (*set).fd_array).cast::<RawFd>();
let fd_count = (*set).fd_count;
for _ in 0..fd_count {
if *slot == fd {

View File

@@ -2217,7 +2217,7 @@ mod _socket {
fn as_slice(&self) -> &[netioapi::MIB_IF_ROW2] {
unsafe {
let p = self.ptr.as_ptr();
let ptr = ptr::addr_of!((*p).Table) as *const netioapi::MIB_IF_ROW2;
let ptr = &raw const (*p).Table as *const netioapi::MIB_IF_ROW2;
std::slice::from_raw_parts(ptr, (*p).NumEntries as usize)
}
}

View File

@@ -76,7 +76,7 @@ mod _sqlite {
ffi::{c_int, c_longlong, c_uint, c_void, CStr},
fmt::Debug,
ops::Deref,
ptr::{addr_of_mut, null, null_mut},
ptr::{null, null_mut},
thread::ThreadId,
};
@@ -1180,14 +1180,10 @@ mod _sqlite {
)
};
// TODO: replace with Result.inspect_err when stable
if let Err(exc) = db.check(ret, vm) {
db.check(ret, vm).inspect_err(|_| {
// create_collation do not call destructor if error occur
let _ = unsafe { Box::from_raw(data) };
Err(exc)
} else {
Ok(())
}
})
}
#[pymethod]
@@ -2398,7 +2394,7 @@ mod _sqlite {
let ret = unsafe {
sqlite3_open_v2(
path,
addr_of_mut!(db),
&raw mut db,
SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE
| if uri { SQLITE_OPEN_URI } else { 0 },

View File

@@ -85,7 +85,7 @@ mod unicodedata {
}
fn check_age(&self, c: char) -> bool {
Age::of(c).map_or(false, |age| age.actual() <= self.unic_version)
Age::of(c).is_some_and(|age| age.actual() <= self.unic_version)
}
fn extract_char(&self, character: PyStrRef, vm: &VirtualMachine) -> PyResult<Option<char>> {

View File

@@ -73,12 +73,12 @@ thiserror = { workspace = true }
thread_local = { workspace = true }
memchr = { workspace = true }
caseless = "0.2.1"
caseless = "0.2.2"
flamer = { version = "0.4", optional = true }
half = "2"
memoffset = "0.9.1"
optional = "0.5.0"
result-like = "0.4.6"
result-like = "0.5.0"
timsort = "0.1.2"
## unicode stuff

View File

@@ -385,17 +385,14 @@ pub trait AnyStr {
let (end_len, i_diff) = match *ch {
b'\n' => (keep, 1),
b'\r' => {
let is_rn = enumerated.peek().map_or(false, |(_, ch)| **ch == b'\n');
let is_rn = enumerated.next_if(|(_, ch)| **ch == b'\n').is_some();
if is_rn {
let _ = enumerated.next();
(keep + keep, 2)
} else {
(keep, 1)
}
}
_ => {
continue;
}
_ => continue,
};
let range = last_i..i + end_len;
last_i = i + i_diff;

View File

@@ -390,7 +390,7 @@ impl PyAsyncGenAThrow {
}
fn ignored_close(&self, res: &PyResult<PyIterReturn>) -> bool {
res.as_ref().map_or(false, |v| match v {
res.as_ref().is_ok_and(|v| match v {
PyIterReturn::Return(obj) => obj.payload_is::<PyAsyncGenWrappedValue>(),
PyIterReturn::StopIteration(_) => false,
})

View File

@@ -127,8 +127,10 @@ impl PyBool {
let lhs = get_value(&lhs);
let rhs = get_value(&rhs);
(lhs || rhs).to_pyobject(vm)
} else if let Some(lhs) = lhs.payload::<PyInt>() {
lhs.or(rhs, vm).to_pyobject(vm)
} else {
get_py_int(&lhs).or(rhs, vm).to_pyobject(vm)
vm.ctx.not_implemented()
}
}
@@ -141,8 +143,10 @@ impl PyBool {
let lhs = get_value(&lhs);
let rhs = get_value(&rhs);
(lhs && rhs).to_pyobject(vm)
} else if let Some(lhs) = lhs.payload::<PyInt>() {
lhs.and(rhs, vm).to_pyobject(vm)
} else {
get_py_int(&lhs).and(rhs, vm).to_pyobject(vm)
vm.ctx.not_implemented()
}
}
@@ -155,8 +159,10 @@ impl PyBool {
let lhs = get_value(&lhs);
let rhs = get_value(&rhs);
(lhs ^ rhs).to_pyobject(vm)
} else if let Some(lhs) = lhs.payload::<PyInt>() {
lhs.xor(rhs, vm).to_pyobject(vm)
} else {
get_py_int(&lhs).xor(rhs, vm).to_pyobject(vm)
vm.ctx.not_implemented()
}
}
}
@@ -207,7 +213,3 @@ pub(crate) fn init(context: &Context) {
pub(crate) fn get_value(obj: &PyObject) -> bool {
!obj.payload::<PyInt>().unwrap().as_bigint().is_zero()
}
fn get_py_int(obj: &PyObject) -> &PyInt {
obj.payload::<PyInt>().unwrap()
}

View File

@@ -67,7 +67,7 @@ impl GetDescriptor for PyMethodDescriptor {
let bound = match obj {
Some(obj) => {
if descr.method.flags.contains(PyMethodFlags::METHOD) {
if cls.map_or(false, |c| c.fast_isinstance(vm.ctx.types.type_type)) {
if cls.is_some_and(|c| c.fast_isinstance(vm.ctx.types.type_type)) {
obj
} else {
return Err(vm.new_type_error(format!(

View File

@@ -728,11 +728,8 @@ impl ExactSizeIterator for DictIter<'_> {
}
#[pyclass]
trait DictView: PyPayload + PyClassDef + Iterable + Representable
where
Self::ReverseIter: PyPayload,
{
type ReverseIter;
trait DictView: PyPayload + PyClassDef + Iterable + Representable {
type ReverseIter: PyPayload;
fn dict(&self) -> &PyDictRef;
fn item(vm: &VirtualMachine, key: PyObjectRef, value: PyObjectRef) -> PyObjectRef;

View File

@@ -506,7 +506,8 @@ impl PyFunction {
zelf.jitted_code
.get_or_try_init(|| {
let arg_types = jitfunc::get_jit_arg_types(&zelf, vm)?;
rustpython_jit::compile(&zelf.code.code, &arg_types)
let ret_type = jitfunc::jit_ret_type(&zelf, vm)?;
rustpython_jit::compile(&zelf.code.code, &arg_types, ret_type)
.map_err(|err| jitfunc::new_jit_error(err.to_string(), vm))
})
.map(drop)

View File

@@ -52,7 +52,7 @@ fn get_jit_arg_type(dict: &PyDictRef, name: &str, vm: &VirtualMachine) -> PyResu
Ok(JitType::Bool)
} else {
Err(new_jit_error(
"Jit requires argument to be either int or float".to_owned(),
"Jit requires argument to be either int, float or bool".to_owned(),
vm,
))
}
@@ -106,6 +106,25 @@ pub fn get_jit_arg_types(func: &Py<PyFunction>, vm: &VirtualMachine) -> PyResult
}
}
pub fn jit_ret_type(func: &Py<PyFunction>, vm: &VirtualMachine) -> PyResult<Option<JitType>> {
let func_obj: PyObjectRef = func.as_ref().to_owned();
let annotations = func_obj.get_attr("__annotations__", vm)?;
if vm.is_none(&annotations) {
Err(new_jit_error(
"Jitting function requires return type to have annotations".to_owned(),
vm,
))
} else if let Ok(dict) = PyDictRef::try_from_object(vm, annotations) {
if dict.contains_key("return", vm) {
get_jit_arg_type(&dict, "return", vm).map_or(Ok(None), |t| Ok(Some(t)))
} else {
Ok(None)
}
} else {
Err(vm.new_type_error("Function annotations aren't a dict".to_owned()))
}
}
fn get_jit_value(vm: &VirtualMachine, obj: &PyObject) -> Result<AbiValue, ArgsError> {
// This does exact type checks as subclasses of int/float can't be passed to jitted functions
let cls = obj.class();

View File

@@ -126,7 +126,7 @@ impl PyMappingProxy {
match &self.mapping {
MappingProxyInner::Class(class) => Ok(key
.as_interned_str(vm)
.map_or(false, |key| class.attributes.read().contains_key(key))),
.is_some_and(|key| class.attributes.read().contains_key(key))),
MappingProxyInner::Mapping(mapping) => mapping.to_sequence().contains(key, vm),
}
}

View File

@@ -903,9 +903,8 @@ impl PyStr {
let end_len = match ch {
'\n' => 1,
'\r' => {
let is_rn = enumerated.peek().map_or(false, |(_, ch)| *ch == '\n');
let is_rn = enumerated.next_if(|(_, ch)| *ch == '\n').is_some();
if is_rn {
let _ = enumerated.next();
2
} else {
1
@@ -913,9 +912,7 @@ impl PyStr {
}
'\x0b' | '\x0c' | '\x1c' | '\x1d' | '\x1e' | '\u{0085}' | '\u{2028}'
| '\u{2029}' => ch.len_utf8(),
_ => {
continue;
}
_ => continue,
};
let range = if args.keepends {
last_i..i + end_len
@@ -1160,7 +1157,7 @@ impl PyStr {
#[pymethod]
fn isidentifier(&self) -> bool {
let mut chars = self.as_str().chars();
let is_identifier_start = chars.next().map_or(false, |c| c == '_' || is_xid_start(c));
let is_identifier_start = chars.next().is_some_and(|c| c == '_' || is_xid_start(c));
// a string is not an identifier if it has whitespace or starts with a number
is_identifier_start && chars.all(is_xid_continue)
}

View File

@@ -296,7 +296,7 @@ pub(crate) fn cformat_bytes(
return if is_mapping
|| values_obj
.payload::<tuple::PyTuple>()
.map_or(false, |e| e.is_empty())
.is_some_and(|e| e.is_empty())
{
for (_, part) in format.iter_mut() {
match part {
@@ -397,7 +397,7 @@ pub(crate) fn cformat_string(
return if is_mapping
|| values_obj
.payload::<tuple::PyTuple>()
.map_or(false, |e| e.is_empty())
.is_some_and(|e| e.is_empty())
{
for (_, part) in format.iter() {
match part {

View File

@@ -55,19 +55,14 @@ const fn zst_ref_out_of_thin_air<T: 'static>(x: T) -> &'static T {
// would never get called anyway if we consider this semantically a Box::leak(Box::new(x))-type
// operation. if T isn't zero-sized, we don't have to worry about it because we'll fail to compile.
std::mem::forget(x);
trait Zst: Sized + 'static {
const THIN_AIR: &'static Self = {
if std::mem::size_of::<Self>() == 0 {
// SAFETY: we just confirmed that Self is zero-sized, so we can
// pull a value of it out of thin air.
unsafe { std::ptr::NonNull::<Self>::dangling().as_ref() }
} else {
panic!("can't use a non-zero-sized type here")
}
};
const {
if std::mem::size_of::<T>() != 0 {
panic!("can't use a non-zero-sized type here")
}
// SAFETY: we just confirmed that T is zero-sized, so we can
// pull a value of it out of thin air.
unsafe { std::ptr::NonNull::<T>::dangling().as_ref() }
}
impl<T: 'static> Zst for T {}
<T as Zst>::THIN_AIR
}
/// Get the [`STATIC_FUNC`](IntoPyNativeFn::STATIC_FUNC) of the passed function. The same

View File

@@ -228,12 +228,8 @@ mod sealed {
}
/// A sealed marker trait for `DictKey` types that always become an exact instance of `str`
pub trait InternableString
where
Self: sealed::SealedInternable + ToPyObject + AsRef<Self::Interned>,
Self::Interned: MaybeInternedString,
{
type Interned: ?Sized;
pub trait InternableString: sealed::SealedInternable + ToPyObject + AsRef<Self::Interned> {
type Interned: MaybeInternedString + ?Sized;
fn into_pyref_exact(self, str_type: PyTypeRef) -> PyRefExact<PyStr>;
}

View File

@@ -13,6 +13,7 @@
#![allow(clippy::upper_case_acronyms)]
#![doc(html_logo_url = "https://raw.githubusercontent.com/RustPython/RustPython/main/logo.png")]
#![doc(html_root_url = "https://docs.rs/rustpython-vm/")]
#![cfg_attr(target_os = "redox", feature(raw_ref_op))]
#[cfg(feature = "flame-it")]
#[macro_use]

View File

@@ -322,7 +322,7 @@ unsafe impl Link for WeakLink {
#[inline(always)]
unsafe fn pointers(target: NonNull<Self::Target>) -> NonNull<Pointers<Self::Target>> {
NonNull::new_unchecked(ptr::addr_of_mut!((*target.as_ptr()).0.payload.pointers))
NonNull::new_unchecked(&raw mut (*target.as_ptr()).0.payload.pointers)
}
}
@@ -364,8 +364,11 @@ impl PyWeak {
fn drop_inner(&self) {
let dealloc = {
let mut guard = unsafe { self.parent.as_ref().lock() };
let offset = memoffset::offset_of!(PyInner<PyWeak>, payload);
let pyinner = (self as *const Self as usize - offset) as *const PyInner<Self>;
let offset = std::mem::offset_of!(PyInner<PyWeak>, payload);
let pyinner = (self as *const Self)
.cast::<u8>()
.wrapping_sub(offset)
.cast::<PyInner<Self>>();
let node_ptr = unsafe { NonNull::new_unchecked(pyinner as *mut Py<Self>) };
// the list doesn't have ownership over its PyRef<PyWeak>! we're being dropped
// right now so that should be obvious!!
@@ -1044,7 +1047,7 @@ impl<T: PyObjectPayload> PyRef<T> {
pub fn leak(pyref: Self) -> &'static Py<T> {
let ptr = pyref.ptr;
std::mem::forget(pyref);
unsafe { &*ptr.as_ptr() }
unsafe { ptr.as_ref() }
}
}

View File

@@ -1,4 +1,4 @@
use std::{fmt, marker::PhantomData};
use std::fmt;
use crate::{
object::{
@@ -16,25 +16,18 @@ pub(in crate::object) struct PyObjVTable {
}
impl PyObjVTable {
pub fn of<T: PyObjectPayload>() -> &'static Self {
struct Helper<T: PyObjectPayload>(PhantomData<T>);
trait VtableHelper {
const VTABLE: PyObjVTable;
pub const fn of<T: PyObjectPayload>() -> &'static Self {
&PyObjVTable {
drop_dealloc: drop_dealloc_obj::<T>,
debug: debug_obj::<T>,
trace: const {
if T::IS_TRACE {
Some(try_trace_obj::<T>)
} else {
None
}
},
}
impl<T: PyObjectPayload> VtableHelper for Helper<T> {
const VTABLE: PyObjVTable = PyObjVTable {
drop_dealloc: drop_dealloc_obj::<T>,
debug: debug_obj::<T>,
trace: {
if T::IS_TRACE {
Some(try_trace_obj::<T>)
} else {
None
}
},
};
}
&Helper::<T>::VTABLE
}
}

View File

@@ -95,7 +95,7 @@ impl PyMapping<'_> {
// PyMapping::Check
#[inline]
pub fn check(obj: &PyObject) -> bool {
Self::find_methods(obj).map_or(false, |x| x.as_ref().check())
Self::find_methods(obj).is_some_and(|x| x.as_ref().check())
}
pub fn find_methods(obj: &PyObject) -> Option<PointerSlot<PyMappingMethods>> {

View File

@@ -429,8 +429,7 @@ mod builtins {
let fd_matches = |obj, expected| {
vm.call_method(obj, "fileno", ())
.and_then(|o| i64::try_from_object(vm, o))
.ok()
.map_or(false, |fd| fd == expected)
.is_ok_and(|fd| fd == expected)
};
// everything is normalish, we can just rely on rustyline to use stdin/stdout

View File

@@ -684,7 +684,7 @@ mod decl {
self.grouper
.as_ref()
.and_then(|g| g.upgrade())
.map_or(false, |ref current_grouper| grouper.is(current_grouper))
.is_some_and(|current_grouper| grouper.is(&current_grouper))
}
}

View File

@@ -62,7 +62,7 @@ pub(crate) mod module {
.as_path()
.parent()
.and_then(|dst_parent| dst_parent.join(&args.src).symlink_metadata().ok())
.map_or(false, |meta| meta.is_dir());
.is_some_and(|meta| meta.is_dir());
let res = if dir {
win_fs::symlink_dir(args.src.path, args.dst.path)
} else {

View File

@@ -142,6 +142,7 @@ pub(super) mod _os {
protocol::PyIterReturn,
recursion::ReprGuard,
types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter},
utils::ToCString,
vm::VirtualMachine,
AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
};
@@ -273,8 +274,8 @@ pub(super) mod _os {
fn remove(path: OsPath, dir_fd: DirFd<0>, vm: &VirtualMachine) -> PyResult<()> {
let [] = dir_fd.0;
let is_junction = cfg!(windows)
&& fs::metadata(&path).map_or(false, |meta| meta.file_type().is_dir())
&& fs::symlink_metadata(&path).map_or(false, |meta| meta.file_type().is_symlink());
&& fs::metadata(&path).is_ok_and(|meta| meta.file_type().is_dir())
&& fs::symlink_metadata(&path).is_ok_and(|meta| meta.file_type().is_symlink());
let res = if is_junction {
fs::remove_dir(&path)
} else {
@@ -1028,6 +1029,14 @@ pub(super) mod _os {
})
}
#[cfg(any(unix, windows))]
#[pyfunction]
fn system(command: PyStrRef, vm: &VirtualMachine) -> PyResult<i32> {
let cstr = command.to_cstring(vm)?;
let x = unsafe { libc::system(cstr.as_ptr()) };
Ok(x)
}
#[derive(FromArgs)]
struct UtimeArgs {
path: OsPath,

View File

@@ -901,13 +901,6 @@ pub mod module {
nix::unistd::pipe2(oflags).map_err(|err| err.into_pyexception(vm))
}
#[pyfunction]
fn system(command: PyStrRef, vm: &VirtualMachine) -> PyResult<i32> {
let cstr = command.to_cstring(vm)?;
let x = unsafe { libc::system(cstr.as_ptr()) };
Ok(x)
}
fn _chmod(
path: OsPath,
dir_fd: DirFd<0>,

View File

@@ -92,7 +92,7 @@ mod pwd {
#[pyfunction]
fn getpwall(vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
// setpwent, getpwent, etc are not thread safe. Could use fgetpwent_r, but this is easier
static GETPWALL: parking_lot::Mutex<()> = parking_lot::const_mutex(());
static GETPWALL: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
let _guard = GETPWALL.lock();
let mut list = Vec::new();

View File

@@ -11,10 +11,12 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
#[pymodule]
pub(crate) mod _signal {
#[cfg(any(unix, windows))]
use crate::{
convert::{IntoPyException, TryFromBorrowedObject},
signal, Py, PyObjectRef, PyResult, VirtualMachine,
Py,
};
use crate::{signal, PyObjectRef, PyResult, VirtualMachine};
use std::sync::atomic::{self, Ordering};
#[cfg(any(unix, windows))]
@@ -110,28 +112,28 @@ pub(crate) mod _signal {
module: &Py<crate::builtins::PyModule>,
vm: &VirtualMachine,
) {
let sig_dfl = vm.new_pyobj(SIG_DFL as u8);
let sig_ign = vm.new_pyobj(SIG_IGN as u8);
for signum in 1..NSIG {
let handler = unsafe { libc::signal(signum as i32, SIG_IGN) };
if handler != SIG_ERR {
unsafe { libc::signal(signum as i32, handler) };
}
let py_handler = if handler == SIG_DFL {
Some(sig_dfl.clone())
} else if handler == SIG_IGN {
Some(sig_ign.clone())
} else {
None
};
vm.signal_handlers.as_deref().unwrap().borrow_mut()[signum] = py_handler;
}
let int_handler = module
.get_attr("default_int_handler", vm)
.expect("_signal does not have this attr?");
if vm.state.settings.install_signal_handlers {
let sig_dfl = vm.new_pyobj(SIG_DFL as u8);
let sig_ign = vm.new_pyobj(SIG_IGN as u8);
for signum in 1..NSIG {
let handler = unsafe { libc::signal(signum as i32, SIG_IGN) };
if handler != SIG_ERR {
unsafe { libc::signal(signum as i32, handler) };
}
let py_handler = if handler == SIG_DFL {
Some(sig_dfl.clone())
} else if handler == SIG_IGN {
Some(sig_ign.clone())
} else {
None
};
vm.signal_handlers.as_deref().unwrap().borrow_mut()[signum] = py_handler;
}
let int_handler = module
.get_attr("default_int_handler", vm)
.expect("_signal does not have this attr?");
signal(libc::SIGINT, int_handler, vm).expect("Failed to set sigint handler");
}
}

View File

@@ -470,7 +470,7 @@ impl PyType {
.attributes
.read()
.get(identifier!(ctx, __hash__))
.map_or(false, |a| a.is(&ctx.none));
.is_some_and(|a| a.is(&ctx.none));
let wrapper = if is_unhashable {
hash_not_implemented
} else {
@@ -945,7 +945,7 @@ pub trait GetDescriptor: PyPayload {
#[inline]
fn _cls_is(cls: &Option<PyObjectRef>, other: &impl Borrow<PyObject>) -> bool {
cls.as_ref().map_or(false, |cls| other.borrow().is(cls))
cls.as_ref().is_some_and(|cls| other.borrow().is(cls))
}
}

View File

@@ -133,13 +133,10 @@ impl VirtualMachine {
let import_func = ctx.none();
let profile_func = RefCell::new(ctx.none());
let trace_func = RefCell::new(ctx.none());
// hack to get around const array repeat expressions, rust issue #79270
const NONE: Option<PyObjectRef> = None;
// putting it in a const optimizes better, prevents linear initialization of the array
#[allow(clippy::declare_interior_mutable_const)]
const SIGNAL_HANDLERS: RefCell<[Option<PyObjectRef>; signal::NSIG]> =
RefCell::new([NONE; signal::NSIG]);
let signal_handlers = Some(Box::new(SIGNAL_HANDLERS));
let signal_handlers = Some(Box::new(
// putting it in a const optimizes better, prevents linear initialization of the array
const { RefCell::new([const { None }; signal::NSIG]) },
));
let module_inits = stdlib::get_module_inits();

View File

@@ -298,8 +298,8 @@ impl VirtualMachine {
}
if let Some(slot_c) = class_c.slots.as_number.left_ternary_op(op_slot) {
if slot_a.map_or(false, |slot_a| (slot_a as usize) != (slot_c as usize))
&& slot_b.map_or(false, |slot_b| (slot_b as usize) != (slot_c as usize))
if slot_a.is_some_and(|slot_a| slot_a != slot_c)
&& slot_b.is_some_and(|slot_b| slot_b != slot_c)
{
let ret = slot_c(a, b, c, self)?;
if !ret.is(&self.ctx.not_implemented) {

View File

@@ -367,7 +367,7 @@ fn setup_context(
// Stack level comparisons to Python code is off by one as there is no
// warnings-related stack level to avoid.
if stack_level <= 0 || f.as_ref().map_or(false, |frame| frame.is_internal_frame()) {
if stack_level <= 0 || f.as_ref().is_some_and(|frame| frame.is_internal_frame()) {
loop {
stack_level -= 1;
if stack_level <= 0 {

View File

@@ -8,7 +8,7 @@ repository = "https://github.com/RustPython/RustPython"
[[module]]
name = "rustpython"
source = "target/wasm32-wasi/release/rustpython.wasm"
source = "target/wasm32-wasip1/release/rustpython.wasm"
abi = "wasi"
[[command]]

View File

@@ -4,24 +4,25 @@
"description": "Bindings to the RustPython library for WebAssembly",
"main": "index.js",
"dependencies": {
"codemirror": "^5.42.0",
"local-echo": "^0.2.0",
"xterm": "^3.8.0"
"@codemirror/lang-python": "^6.1.6",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.3.0",
"codemirror": "^6.0.1",
"upgrade": "^1.1.0",
"xterm-readline": "^1.1.2"
},
"devDependencies": {
"@wasm-tool/wasm-pack-plugin": "^1.1.0",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.4.1",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.9.0",
"raw-loader": "^4.0.0",
"serve": "^11.0.2",
"webpack": "^4.16.3",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.5"
"@wasm-tool/wasm-pack-plugin": "^1.7.0",
"css-loader": "^7.1.2",
"html-webpack-plugin": "^5.6.3",
"mini-css-extract-plugin": "^2.9.2",
"serve": "^14.2.4",
"webpack": "^5.97.1",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.0"
},
"scripts": {
"dev": "webpack-dev-server -d",
"dev": "webpack serve",
"build": "webpack",
"dist": "webpack --mode production",
"test": "webpack --mode production && cd ../tests && pytest"

View File

@@ -1,13 +1,12 @@
import asyncweb
import whlimport
whlimport.setup()
# make sys.modules['os'] a dumb version of the os module, which has posixpath
# available as os.path as well as a few other utilities, but will raise an
# OSError for anything that actually requires an OS
import _dummy_os
_dummy_os._shim()
import asyncweb
import whlimport
whlimport.setup()
@asyncweb.main
async def main():

View File

@@ -14,7 +14,6 @@
browser's devtools and play with <code>rp.pyEval('1 + 1')</code>
</p>
<div id="code-wrapper">
<textarea id="code"><%= defaultSnippet %></textarea>
<select id="snippets">
<% for (const name of snippets) { %>
<option
@@ -77,7 +76,7 @@
<a href="https://github.com/RustPython/RustPython">
<img
style="position: absolute; top: 0; right: 0; border: 0;"
src="https://s3.amazonaws.com/github/ribbons/forkme_right_green_007200.png"
src="https://github.blog/wp-content/uploads/2008/12/forkme_right_green_007200.png"
alt="Fork me on GitHub"
/>
</a>

View File

@@ -1,11 +1,13 @@
import './style.css';
import 'xterm/lib/xterm.css';
import CodeMirror from 'codemirror';
import 'codemirror/mode/python/python';
import 'codemirror/addon/comment/comment';
import 'codemirror/lib/codemirror.css';
import { Terminal } from 'xterm';
import LocalEchoController from 'local-echo';
import '@xterm/xterm/css/xterm.css';
import { EditorView, basicSetup } from 'codemirror';
import { keymap } from '@codemirror/view';
import { indentUnit } from '@codemirror/language';
import { indentWithTab } from '@codemirror/commands';
import { python } from '@codemirror/lang-python';
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import { Readline } from 'xterm-readline';
let rp;
@@ -22,23 +24,24 @@ import('rustpython')
document.getElementById('error').textContent = e;
});
const editor = CodeMirror.fromTextArea(document.getElementById('code'), {
extraKeys: {
'Ctrl-Enter': runCodeFromTextarea,
'Cmd-Enter': runCodeFromTextarea,
'Shift-Tab': 'indentLess',
'Ctrl-/': 'toggleComment',
'Cmd-/': 'toggleComment',
Tab: (editor) => {
var spaces = Array(editor.getOption('indentUnit') + 1).join(' ');
editor.replaceSelection(spaces);
},
},
lineNumbers: true,
mode: 'text/x-python',
indentUnit: 4,
autofocus: true,
const fixedHeightEditor = EditorView.theme({
'&': { height: '100%' },
'.cm-scroller': { overflow: 'auto' },
});
const editor = new EditorView({
parent: document.getElementById('code-wrapper'),
extensions: [
basicSetup,
python(),
keymap.of(
{ key: 'Ctrl-Enter', mac: 'Cmd-Enter', run: runCodeFromTextarea },
indentWithTab,
),
indentUnit.of(' '),
fixedHeightEditor,
],
});
editor.focus();
const consoleElement = document.getElementById('console');
const errorElement = document.getElementById('error');
@@ -48,7 +51,7 @@ function runCodeFromTextarea() {
consoleElement.value = '';
errorElement.textContent = '';
const code = editor.getValue();
const code = editor.state.doc.toString();
try {
rp.pyExec(code, {
stdout: (output) => {
@@ -78,18 +81,25 @@ function updateSnippet() {
// the require here creates a webpack context; it's fine to use it
// dynamically.
// https://webpack.js.org/guides/dependency-management/
const { default: snippet } = require(
`raw-loader!../snippets/${selected}.py`,
);
const snippet = require(`../snippets/${selected}.py?raw`);
editor.setValue(snippet);
runCodeFromTextarea();
editor.dispatch({
changes: { from: 0, to: editor.state.doc.length, insert: snippet },
});
}
function updateSnippetAndRun() {
updateSnippet();
requestAnimationFrame(runCodeFromTextarea);
}
updateSnippet();
const term = new Terminal();
const readline = new Readline();
const fitAddon = new FitAddon();
term.loadAddon(readline);
term.loadAddon(fitAddon);
term.open(document.getElementById('terminal'));
const localEcho = new LocalEchoController(term);
fitAddon.fit();
let terminalVM;
@@ -107,40 +117,33 @@ finally:
}
async function readPrompts() {
let continuing = false;
let continuing = '';
while (true) {
const ps1 = getPrompt('ps1');
const ps2 = getPrompt('ps2');
let input;
let input = await readline.read(getPrompt(continuing ? 'ps2' : 'ps1'));
if (input.endsWith('\n')) input = input.slice(0, -1);
if (continuing) {
const prom = localEcho.read(ps2, ps2);
localEcho._activePrompt.prompt = ps1;
localEcho._input = localEcho.history.entries.pop() + '\n';
localEcho._cursor = localEcho._input.length;
localEcho._active = true;
input = await prom;
if (!input.endsWith('\n')) continue;
} else {
input = await localEcho.read(ps1, ps2);
input = continuing += '\n' + input;
if (!continuing.endsWith('\n')) continue;
}
try {
console.log([input]);
terminalVM.execSingle(input);
} catch (err) {
if (err.canContinue) {
continuing = true;
continuing = input;
continue;
} else if (err instanceof WebAssembly.RuntimeError) {
err = window.__RUSTPYTHON_ERROR || err;
}
localEcho.println(err);
readline.print('' + err);
}
continuing = false;
continuing = '';
}
}
function onReady() {
snippets.addEventListener('change', updateSnippet);
snippets.addEventListener('change', updateSnippetAndRun);
document
.getElementById('run-btn')
.addEventListener('click', runCodeFromTextarea);
@@ -148,7 +151,7 @@ function onReady() {
runCodeFromTextarea();
terminalVM = rp.vmStore.init('term_vm');
terminalVM.setStdout((data) => localEcho.print(data));
terminalVM.setStdout((data) => readline.print(data));
readPrompts().catch((err) => console.error(err));
// so that the test knows that we're ready

View File

@@ -3,7 +3,7 @@ textarea {
resize: vertical;
}
#code,
#code-wrapper,
#console {
height: 30vh;
width: calc(100% - 3px);

View File

@@ -1,7 +1,6 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const path = require('path');
const fs = require('fs');
@@ -12,6 +11,7 @@ module.exports = (env = {}) => {
output: {
path: path.join(__dirname, 'dist'),
filename: 'index.js',
clean: true,
},
mode: 'development',
resolve: {
@@ -28,10 +28,17 @@ module.exports = (env = {}) => {
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{
resourceQuery: '?raw',
type: 'asset/source',
},
{
test: /\.wasm$/,
type: 'webassembly/async',
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/index.ejs',
@@ -51,6 +58,9 @@ module.exports = (env = {}) => {
filename: 'styles.css',
}),
],
experiments: {
asyncWebAssembly: true,
},
};
if (!env.noWasmPack) {
config.plugins.push(

View File

@@ -3,7 +3,7 @@
use crate::js_module;
use crate::vm_class::{stored_vm_from_wasm, WASMVirtualMachine};
use js_sys::{Array, ArrayBuffer, Object, Promise, Reflect, SyntaxError, Uint8Array};
use rustpython_parser::ParseErrorType;
use rustpython_parser::{lexer::LexicalErrorType, ParseErrorType};
use rustpython_vm::{
builtins::PyBaseExceptionRef,
compiler::{CompileError, CompileErrorType},
@@ -34,7 +34,7 @@ extern "C" {
pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyBaseExceptionRef) -> JsValue {
let jserr = vm.try_class("_js", "JSError").ok();
let js_arg = if jserr.map_or(false, |jserr| py_err.fast_isinstance(&jserr)) {
let js_arg = if jserr.is_some_and(|jserr| py_err.fast_isinstance(&jserr)) {
py_err.get_arg(0)
} else {
None
@@ -258,7 +258,15 @@ pub fn syntax_err(err: CompileError) -> SyntaxError {
&"col".into(),
&(err.location.unwrap().column.get()).into(),
);
let can_continue = matches!(&err.error, CompileErrorType::Parse(ParseErrorType::Eof));
let can_continue = matches!(
&err.error,
CompileErrorType::Parse(
ParseErrorType::Eof
| ParseErrorType::Lexical(LexicalErrorType::Eof)
| ParseErrorType::Lexical(LexicalErrorType::IndentationError)
| ParseErrorType::UnrecognizedToken(rustpython_parser::Tok::Dedent, _)
)
);
let _ = Reflect::set(&js_err, &"canContinue".into(), &can_continue.into());
js_err
}