Compare commits

...

44 Commits

Author SHA1 Message Date
Ashwin Naren
d7a72b5755 add constants and implement functions
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-24 16:16:34 +09:00
Ashwin Naren
1f3a9672c3 Add _winapi.GetACP and enable test_unicode on windows (#5547)
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-24 13:21:02 +09:00
Axect
31c5c3eb9d Update puruspe version to 0.4.0
To resolve the issue (#5496)
2025-02-24 11:15:33 +09:00
Ashwin Naren
7fada8b97e fix _ctypes error names
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-23 16:07:52 +09:00
Ashwin Naren
429754fd33 Fix unicode decode bug on surrogate error mode (#5546)
* subtract with overflow to check for whether to use surrogate

* enable test_argparse for windows on ci

------

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-23 16:07:22 +09:00
Ashwin Naren
b4f0a589ed platform-dependent Windows testing (#5536)
* disable test_argparse on windows

* fix test_exceptions and mark it as platform dependent

* test importlib on windows

* explain why windows tests fail

* mark test_argparse as non platform-independent

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-23 09:48:02 +09:00
Noa
331a3c108f Switch to criterion in sre_engine benchmarks 2025-02-23 09:44:57 +09:00
Ashwin Naren
d698b28ce5 Ensure pymethod cannot be both magic and named simultaneously + macro documentation (#5538)
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-22 17:22:31 +09:00
Ashwin Naren
23236aa8c7 test_datetime now works on windows
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-21 15:09:40 +09:00
Noa
a9331bb34d Fix warnings for rust 1.85 2025-02-20 14:58:59 -06:00
Hanif Ariffin
65dcf1ce1c Updating test_math.py to CPython 3.12.9 (#5507)
* Fixed implementation against CPython 3.12.9 Lib/test/test_math.py tests
---------

Signed-off-by: Hanif Ariffin <hanif.ariffin.4326@gmail.com>
Co-authored-by: Jeong YunWon <jeong@youknowone.org>
2025-02-20 11:21:12 +09:00
Ashwin Naren
e2b0fe4266 _ctypes pt. 2 (#5524)
* add __version__

* add more types/constants

* shared library and ExternalLibs implementation

* FreeLibrary for windows

* fixed PyCSimple

* LoadLibrary and FreeLibrary for non-windows

* fault-tolerant float equality

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-20 10:50:10 +09:00
Noa
fa2acd7cde Update rand to 0.9 2025-02-18 17:07:26 +09:00
Ashwin Naren
a71c16f8cb test colorize on ci
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 16:52:04 +09:00
Ashwin Naren
f466971312 clippy
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 15:50:27 +09:00
Ashwin Naren
69b1a9910f formatting
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 15:50:27 +09:00
Ashwin Naren
4ed735b424 time.daylight for windows
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 15:50:27 +09:00
Ashwin Naren
175afd97d8 time.timezone for windows
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 15:50:27 +09:00
Ashwin Naren
72338d578b tzname on windows
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 15:50:27 +09:00
Ashwin Naren
9856d94f2d function to retrieve tz info on windows
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 15:50:27 +09:00
Ashwin Naren
517ffed401 fix clippy lint
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-17 14:15:57 -06:00
Ashwin Naren
38a6a8d984 duplicate windows-sys
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-17 14:15:57 -06:00
Ashwin Naren
630c1ff8d1 simple part of the bump
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-17 14:15:57 -06:00
Ashwin Naren
7e1568a1ff Revert "windows-rs upgrade to 0.59"
This reverts commit 547530724e.
2025-02-17 14:15:57 -06:00
Ashwin Naren
6788010f7d windows-rs upgrade to 0.59 2025-02-17 14:15:57 -06:00
Ashwin Naren
9e310934d3 fix panic
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-17 12:00:39 -06:00
Ashwin Naren
e8a3406624 itertools upgrade 2025-02-16 10:20:56 +09:00
Ashwin Naren
fde87a340c Initial _ctypes implementation (#5519)
* initial _ctypes implementation with _CData, get_errno, and set_errno

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-15 16:03:58 +09:00
Jeong, YunWon
19050afc3f Merge pull request #5520 from arihant2math/colorize
Add _colorize at 3.13.2
2025-02-14 15:34:26 +09:00
Ashwin Naren
e96557b3e1 add _colorize.py at 3.13.2
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-13 20:11:36 -08:00
Ashwin Naren
a5364973d9 implement nt._supports_virtual_terminal
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-13 20:11:05 -08:00
Ashwin Naren
a46ce8ec3a Mark version 3.13.0 (#5495)
* bump to 3.13.1
* fix some tests
* strip left whitespace from doc
* remove specific difflib test that was causing issues
* fix test_enum

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-13 14:11:01 +09:00
Ashwin Naren
6e35e20e49 dependency bump
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-12 18:16:45 +09:00
Noa
2d5e4d89b0 Update openssl to fix possible vulnerability 2025-02-12 15:57:41 +09:00
Hanif Ariffin
c9e62002ec Fixed the implementation of some math functions to match CPython closer. (#5510)
Signed-off-by: Hanif Ariffin <hanif.ariffin.4326@gmail.com>
2025-02-11 17:09:38 +09:00
Lee Dogeon
465627f104 Implement vm logics related with ParamSpec, TypeVarTuple 2025-02-10 21:21:38 +09:00
Ashwin Naren
3de1c2ab56 Update malachite-q and base to 0.4.22 (#5499)
* update malachite-q and base to 0.4.22

* Update malachite-bigint from parser

---------

Co-authored-by: Jeong YunWon <jeong@youknowone.org>
2025-02-10 15:55:08 +09:00
Jeong, YunWon
8f5cc6174c fix windows sleep 2025-02-07 07:53:28 +09:00
Jeong YunWon
29d014a0e1 Pin malachite versions to avoid API incompatibility 2025-02-03 11:57:30 +09:00
Ashwin Naren
396a0ca563 Basic Match statements (#5485)
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-01-25 23:14:15 +09:00
Jeong YunWon
a500178b3c update parser to fix match crash 2025-01-22 13:41:01 +09:00
Jeong YunWon
7d770f55fb more assertions in switch_to_block 2025-01-21 23:53:23 +09:00
Jeong, YunWon
db283a66e8 Merge pull request #5477 from youknowone/better-downcast-error
Add better panic for abnormal downcast error
2025-01-21 13:54:45 +09:00
Jeong YunWon
5ad7e97e05 Add better panic for abnormal downcast error 2025-01-16 00:57:09 +09:00
84 changed files with 3191 additions and 797 deletions

View File

@@ -17,12 +17,17 @@ concurrency:
env:
CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,sqlite,ssl
# Skip additional tests on Windows. They are checked on Linux and MacOS.
# test_glob: many failing tests
# test_io: many failing tests
# test_os: many failing tests
# test_pathlib: support.rmtree() failing
# test_posixpath: OSError: (22, 'The filename, directory name, or volume label syntax is incorrect. (os error 123)')
# test_venv: couple of failing tests
WINDOWS_SKIPS: >-
test_datetime
test_glob
test_importlib
test_io
test_os
test_rlcompleter
test_pathlib
test_posixpath
test_venv
@@ -34,7 +39,7 @@ env:
# PLATFORM_INDEPENDENT_TESTS are tests that do not depend on the underlying OS. They are currently
# only run on Linux to speed up the CI.
PLATFORM_INDEPENDENT_TESTS: >-
test_argparse
test__colorize
test_array
test_asyncgen
test_binop
@@ -59,7 +64,6 @@ env:
test_dis
test_enumerate
test_exception_variations
test_exceptions
test_float
test_format
test_fractions
@@ -100,12 +104,11 @@ env:
test_tuple
test_types
test_unary
test_unicode
test_unpack
test_weakref
test_yield_from
# Python version targeted by the CI.
PYTHON_VERSION: "3.12.3"
PYTHON_VERSION: "3.13.1"
jobs:
rust_tests:
@@ -378,7 +381,8 @@ jobs:
with: { wabt-version: "1.0.30" }
- name: check wasm32-unknown without js
run: |
cargo build --release --manifest-path wasm/wasm-unknown-test/Cargo.toml --target wasm32-unknown-unknown --verbose
cd wasm/wasm-unknown-test
cargo build --release --verbose
if wasm-objdump -xj Import target/wasm32-unknown-unknown/release/wasm_unknown_test.wasm; then
echo "ERROR: wasm32-unknown module expects imports from the host environment" >2
fi

View File

@@ -7,7 +7,7 @@ name: Periodic checks/tasks
env:
CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,ssl,jit
PYTHON_VERSION: "3.12.0"
PYTHON_VERSION: "3.13.1"
jobs:
# codecov collects code coverage data from the rust tests, python snippets and python test suite.

808
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,7 +47,7 @@ libc = { workspace = true }
rustyline = { workspace = true }
[dev-dependencies]
criterion = { version = "0.3.5", features = ["html_reports"] }
criterion = { workspace = true }
pyo3 = { version = "0.22", features = ["auto-initialize"] }
[[bench]]
@@ -104,7 +104,7 @@ members = [
version = "0.4.0"
authors = ["RustPython Team"]
edition = "2021"
rust-version = "1.83.0"
rust-version = "1.85.0"
repository = "https://github.com/RustPython/RustPython"
license = "MIT"
@@ -122,16 +122,16 @@ rustpython-stdlib = { path = "stdlib", default-features = false, version = "0.4.
rustpython-sre_engine = { path = "vm/sre_engine", version = "0.4.0" }
rustpython-doc = { git = "https://github.com/RustPython/__doc__", tag = "0.3.0", version = "0.3.0" }
rustpython-literal = { version = "0.4.0" }
rustpython-parser-core = { version = "0.4.0" }
rustpython-parser = { version = "0.4.0" }
rustpython-ast = { version = "0.4.0" }
rustpython-format= { version = "0.4.0" }
# rustpython-literal = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "00d2f1d1a7522ef9c85c10dfa5f0bb7178dee655" }
# rustpython-parser-core = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "00d2f1d1a7522ef9c85c10dfa5f0bb7178dee655" }
# rustpython-parser = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "00d2f1d1a7522ef9c85c10dfa5f0bb7178dee655" }
# rustpython-ast = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "00d2f1d1a7522ef9c85c10dfa5f0bb7178dee655" }
# rustpython-format = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "00d2f1d1a7522ef9c85c10dfa5f0bb7178dee655" }
# rustpython-literal = { version = "0.4.0" }
# rustpython-parser-core = { version = "0.4.0" }
# rustpython-parser = { version = "0.4.0" }
# rustpython-ast = { version = "0.4.0" }
# rustpython-format= { version = "0.4.0" }
rustpython-literal = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
rustpython-parser-core = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
rustpython-ast = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
rustpython-format = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
# rustpython-literal = { path = "../RustPython-parser/literal" }
# rustpython-parser-core = { path = "../RustPython-parser/core" }
# rustpython-parser = { path = "../RustPython-parser/parser" }
@@ -139,50 +139,51 @@ rustpython-format= { version = "0.4.0" }
# rustpython-format = { path = "../RustPython-parser/format" }
ahash = "0.8.11"
ascii = "1.0"
bitflags = "2.4.1"
ascii = "1.1"
bitflags = "2.4.2"
bstr = "1"
cfg-if = "1.0"
chrono = "0.4.37"
crossbeam-utils = "0.8.19"
chrono = "0.4.39"
criterion = { version = "0.3.5", features = ["html_reports"] }
crossbeam-utils = "0.8.21"
flame = "0.2.2"
getrandom = "0.2.12"
getrandom = "0.3"
glob = "0.3"
hex = "0.4.3"
indexmap = { version = "2.2.6", features = ["std"] }
insta = "1.38.0"
itertools = "0.11.0"
is-macro = "0.3.0"
junction = "1.0.0"
libc = "0.2.153"
log = "0.4.16"
itertools = "0.14.0"
is-macro = "0.3.7"
junction = "1.2.0"
libc = "0.2.169"
log = "0.4.25"
nix = { version = "0.29", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] }
malachite-bigint = "0.2.0"
malachite-q = "0.4.4"
malachite-base = "0.4.4"
memchr = "2.7.2"
num-complex = "0.4.0"
num-integer = "0.1.44"
malachite-bigint = "0.2.3"
malachite-q = "0.4.22"
malachite-base = "0.4.22"
memchr = "2.7.4"
num-complex = "0.4.6"
num-integer = "0.1.46"
num-traits = "0.2"
num_enum = { version = "0.7", default-features = false }
once_cell = "1.19.0"
parking_lot = "0.12.1"
paste = "1.0.7"
rand = "0.8.5"
once_cell = "1.20.3"
parking_lot = "0.12.3"
paste = "1.0.15"
rand = "0.9"
rustix = { version = "0.38", features = ["event"] }
rustyline = "14.0.0"
rustyline = "15.0.0"
serde = { version = "1.0.133", default-features = false }
schannel = "0.1.22"
schannel = "0.1.27"
static_assertions = "1.1"
strum = "0.26"
strum_macros = "0.26"
strum = "0.27"
strum_macros = "0.27"
syn = "1.0.109"
thiserror = "1.0"
thread_local = "1.1.4"
unicode_names2 = "1.2.0"
thiserror = "2.0"
thread_local = "1.1.8"
unicode_names2 = "1.3.0"
widestring = "1.1.0"
windows-sys = "0.52.0"
wasm-bindgen = "0.2.92"
wasm-bindgen = "0.2.100"
# Lints

View File

@@ -25,7 +25,7 @@ RustPython requires the following:
stable version: `rustup update stable`
- If you do not have Rust installed, use [rustup](https://rustup.rs/) to
do so.
- CPython version 3.12 or higher
- CPython version 3.13 or higher
- CPython can be installed by your operating system's package manager,
from the [Python website](https://www.python.org/downloads/), or
using a third-party distribution, such as

67
Lib/_colorize.py vendored Normal file
View File

@@ -0,0 +1,67 @@
import io
import os
import sys
COLORIZE = True
class ANSIColors:
BOLD_GREEN = "\x1b[1;32m"
BOLD_MAGENTA = "\x1b[1;35m"
BOLD_RED = "\x1b[1;31m"
GREEN = "\x1b[32m"
GREY = "\x1b[90m"
MAGENTA = "\x1b[35m"
RED = "\x1b[31m"
RESET = "\x1b[0m"
YELLOW = "\x1b[33m"
NoColors = ANSIColors()
for attr in dir(NoColors):
if not attr.startswith("__"):
setattr(NoColors, attr, "")
def get_colors(colorize: bool = False, *, file=None) -> ANSIColors:
if colorize or can_colorize(file=file):
return ANSIColors()
else:
return NoColors
def can_colorize(*, file=None) -> bool:
if file is None:
file = sys.stdout
if not sys.flags.ignore_environment:
if os.environ.get("PYTHON_COLORS") == "0":
return False
if os.environ.get("PYTHON_COLORS") == "1":
return True
if os.environ.get("NO_COLOR"):
return False
if not COLORIZE:
return False
if os.environ.get("FORCE_COLOR"):
return True
if os.environ.get("TERM") == "dumb":
return False
if not hasattr(file, "fileno"):
return False
if sys.platform == "win32":
try:
import nt
if not nt._supports_virtual_terminal():
return False
except (ImportError, AttributeError):
return False
try:
return os.isatty(file.fileno())
except io.UnsupportedOperation:
return file.isatty()

19
Lib/difflib.py vendored
View File

@@ -1200,25 +1200,6 @@ def context_diff(a, b, fromfile='', tofile='',
strings for 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
The modification times are normally expressed in the ISO 8601 format.
If not specified, the strings default to blanks.
Example:
>>> print(''.join(context_diff('one\ntwo\nthree\nfour\n'.splitlines(True),
... 'zero\none\ntree\nfour\n'.splitlines(True), 'Original', 'Current')),
... end="")
*** Original
--- Current
***************
*** 1,4 ****
one
! two
! three
four
--- 1,4 ----
+ zero
one
! tree
four
"""
_check_types(a, b, fromfile, tofile, fromfiledate, tofiledate, lineterm)

183
Lib/test/ieee754.txt vendored Normal file
View File

@@ -0,0 +1,183 @@
======================================
Python IEEE 754 floating point support
======================================
>>> from sys import float_info as FI
>>> from math import *
>>> PI = pi
>>> E = e
You must never compare two floats with == because you are not going to get
what you expect. We treat two floats as equal if the difference between them
is small than epsilon.
>>> EPS = 1E-15
>>> def equal(x, y):
... """Almost equal helper for floats"""
... return abs(x - y) < EPS
NaNs and INFs
=============
In Python 2.6 and newer NaNs (not a number) and infinity can be constructed
from the strings 'inf' and 'nan'.
>>> INF = float('inf')
>>> NINF = float('-inf')
>>> NAN = float('nan')
>>> INF
inf
>>> NINF
-inf
>>> NAN
nan
The math module's ``isnan`` and ``isinf`` functions can be used to detect INF
and NAN:
>>> isinf(INF), isinf(NINF), isnan(NAN)
(True, True, True)
>>> INF == -NINF
True
Infinity
--------
Ambiguous operations like ``0 * inf`` or ``inf - inf`` result in NaN.
>>> INF * 0
nan
>>> INF - INF
nan
>>> INF / INF
nan
However unambiguous operations with inf return inf:
>>> INF * INF
inf
>>> 1.5 * INF
inf
>>> 0.5 * INF
inf
>>> INF / 1000
inf
Not a Number
------------
NaNs are never equal to another number, even itself
>>> NAN == NAN
False
>>> NAN < 0
False
>>> NAN >= 0
False
All operations involving a NaN return a NaN except for nan**0 and 1**nan.
>>> 1 + NAN
nan
>>> 1 * NAN
nan
>>> 0 * NAN
nan
>>> 1 ** NAN
1.0
>>> NAN ** 0
1.0
>>> 0 ** NAN
nan
>>> (1.0 + FI.epsilon) * NAN
nan
Misc Functions
==============
The power of 1 raised to x is always 1.0, even for special values like 0,
infinity and NaN.
>>> pow(1, 0)
1.0
>>> pow(1, INF)
1.0
>>> pow(1, -INF)
1.0
>>> pow(1, NAN)
1.0
The power of 0 raised to x is defined as 0, if x is positive. Negative
finite values are a domain error or zero division error and NaN result in a
silent NaN.
>>> pow(0, 0)
1.0
>>> pow(0, INF)
0.0
>>> pow(0, -INF)
inf
>>> 0 ** -1
Traceback (most recent call last):
...
ZeroDivisionError: 0.0 cannot be raised to a negative power
>>> pow(0, NAN)
nan
Trigonometric Functions
=======================
>>> sin(INF)
Traceback (most recent call last):
...
ValueError: math domain error
>>> sin(NINF)
Traceback (most recent call last):
...
ValueError: math domain error
>>> sin(NAN)
nan
>>> cos(INF)
Traceback (most recent call last):
...
ValueError: math domain error
>>> cos(NINF)
Traceback (most recent call last):
...
ValueError: math domain error
>>> cos(NAN)
nan
>>> tan(INF)
Traceback (most recent call last):
...
ValueError: math domain error
>>> tan(NINF)
Traceback (most recent call last):
...
ValueError: math domain error
>>> tan(NAN)
nan
Neither pi nor tan are exact, but you can assume that tan(pi/2) is a large value
and tan(pi) is a very small value:
>>> tan(PI/2) > 1E10
True
>>> -tan(-PI/2) > 1E10
True
>>> tan(PI) < 1E-15
True
>>> asin(NAN), acos(NAN), atan(NAN)
(nan, nan, nan)
>>> asin(INF), asin(NINF)
Traceback (most recent call last):
...
ValueError: math domain error
>>> acos(INF), acos(NINF)
Traceback (most recent call last):
...
ValueError: math domain error
>>> equal(atan(INF), PI/2), equal(atan(NINF), -PI/2)
(True, True)
Hyberbolic Functions
====================

135
Lib/test/test__colorize.py vendored Normal file
View File

@@ -0,0 +1,135 @@
import contextlib
import io
import sys
import unittest
import unittest.mock
import _colorize
from test.support.os_helper import EnvironmentVarGuard
@contextlib.contextmanager
def clear_env():
with EnvironmentVarGuard() as mock_env:
for var in "FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS":
mock_env.unset(var)
yield mock_env
def supports_virtual_terminal():
if sys.platform == "win32":
return unittest.mock.patch("nt._supports_virtual_terminal", return_value=True)
else:
return contextlib.nullcontext()
class TestColorizeFunction(unittest.TestCase):
def test_colorized_detection_checks_for_environment_variables(self):
def check(env, fallback, expected):
with (self.subTest(env=env, fallback=fallback),
clear_env() as mock_env):
mock_env.update(env)
isatty_mock.return_value = fallback
stdout_mock.isatty.return_value = fallback
self.assertEqual(_colorize.can_colorize(), expected)
with (unittest.mock.patch("os.isatty") as isatty_mock,
unittest.mock.patch("sys.stdout") as stdout_mock,
supports_virtual_terminal()):
stdout_mock.fileno.return_value = 1
for fallback in False, True:
check({}, fallback, fallback)
check({'TERM': 'dumb'}, fallback, False)
check({'TERM': 'xterm'}, fallback, fallback)
check({'TERM': ''}, fallback, fallback)
check({'FORCE_COLOR': '1'}, fallback, True)
check({'FORCE_COLOR': '0'}, fallback, True)
check({'FORCE_COLOR': ''}, fallback, fallback)
check({'NO_COLOR': '1'}, fallback, False)
check({'NO_COLOR': '0'}, fallback, False)
check({'NO_COLOR': ''}, fallback, fallback)
check({'TERM': 'dumb', 'FORCE_COLOR': '1'}, False, True)
check({'FORCE_COLOR': '1', 'NO_COLOR': '1'}, True, False)
for ignore_environment in False, True:
# Simulate running with or without `-E`.
flags = unittest.mock.MagicMock(ignore_environment=ignore_environment)
with unittest.mock.patch("sys.flags", flags):
check({'PYTHON_COLORS': '1'}, True, True)
check({'PYTHON_COLORS': '1'}, False, not ignore_environment)
check({'PYTHON_COLORS': '0'}, True, ignore_environment)
check({'PYTHON_COLORS': '0'}, False, False)
for fallback in False, True:
check({'PYTHON_COLORS': 'x'}, fallback, fallback)
check({'PYTHON_COLORS': ''}, fallback, fallback)
check({'TERM': 'dumb', 'PYTHON_COLORS': '1'}, False, not ignore_environment)
check({'NO_COLOR': '1', 'PYTHON_COLORS': '1'}, False, not ignore_environment)
check({'FORCE_COLOR': '1', 'PYTHON_COLORS': '0'}, True, ignore_environment)
@unittest.skipUnless(sys.platform == "win32", "requires Windows")
def test_colorized_detection_checks_on_windows(self):
with (clear_env(),
unittest.mock.patch("os.isatty") as isatty_mock,
unittest.mock.patch("sys.stdout") as stdout_mock,
supports_virtual_terminal() as vt_mock):
stdout_mock.fileno.return_value = 1
isatty_mock.return_value = True
stdout_mock.isatty.return_value = True
vt_mock.return_value = True
self.assertEqual(_colorize.can_colorize(), True)
vt_mock.return_value = False
self.assertEqual(_colorize.can_colorize(), False)
import nt
del nt._supports_virtual_terminal
self.assertEqual(_colorize.can_colorize(), False)
def test_colorized_detection_checks_for_std_streams(self):
with (clear_env(),
unittest.mock.patch("os.isatty") as isatty_mock,
unittest.mock.patch("sys.stdout") as stdout_mock,
unittest.mock.patch("sys.stderr") as stderr_mock,
supports_virtual_terminal()):
stdout_mock.fileno.return_value = 1
stderr_mock.fileno.side_effect = ZeroDivisionError
stderr_mock.isatty.side_effect = ZeroDivisionError
isatty_mock.return_value = True
stdout_mock.isatty.return_value = True
self.assertEqual(_colorize.can_colorize(), True)
isatty_mock.return_value = False
stdout_mock.isatty.return_value = False
self.assertEqual(_colorize.can_colorize(), False)
def test_colorized_detection_checks_for_file(self):
with clear_env(), supports_virtual_terminal():
with unittest.mock.patch("os.isatty") as isatty_mock:
file = unittest.mock.MagicMock()
file.fileno.return_value = 1
isatty_mock.return_value = True
self.assertEqual(_colorize.can_colorize(file=file), True)
isatty_mock.return_value = False
self.assertEqual(_colorize.can_colorize(file=file), False)
# No file.fileno.
with unittest.mock.patch("os.isatty", side_effect=ZeroDivisionError):
file = unittest.mock.MagicMock(spec=['isatty'])
file.isatty.return_value = True
self.assertEqual(_colorize.can_colorize(file=file), False)
# file.fileno() raises io.UnsupportedOperation.
with unittest.mock.patch("os.isatty", side_effect=ZeroDivisionError):
file = unittest.mock.MagicMock()
file.fileno.side_effect = io.UnsupportedOperation
file.isatty.return_value = True
self.assertEqual(_colorize.can_colorize(file=file), True)
file.isatty.return_value = False
self.assertEqual(_colorize.can_colorize(file=file), False)
if __name__ == "__main__":
unittest.main()

View File

@@ -26,7 +26,6 @@ codecs.register(codec_search_function)
codecname = 'testcodec'
class CharmapCodecTest(unittest.TestCase):
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_constructorx(self):
self.assertEqual(str(b'abc', codecname), 'abc')
self.assertEqual(str(b'xdef', codecname), 'abcdef')
@@ -43,14 +42,12 @@ class CharmapCodecTest(unittest.TestCase):
self.assertEqual('dxf'.encode(codecname), b'dabcf')
self.assertEqual('dxfx'.encode(codecname), b'dabcfabc')
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_constructory(self):
self.assertEqual(str(b'ydef', codecname), 'def')
self.assertEqual(str(b'defy', codecname), 'def')
self.assertEqual(str(b'dyf', codecname), 'df')
self.assertEqual(str(b'dyfy', codecname), 'df')
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_maptoundefined(self):
self.assertRaises(UnicodeError, str, b'abc\001', codecname)

View File

@@ -1827,7 +1827,6 @@ class CodecsModuleTest(unittest.TestCase):
self.assertEqual(codecs.decode(b'[\xff]', 'ascii', errors='ignore'),
'[]')
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_encode(self):
self.assertEqual(codecs.encode('\xe4\xf6\xfc', 'latin-1'),
b'\xe4\xf6\xfc')
@@ -1846,7 +1845,6 @@ class CodecsModuleTest(unittest.TestCase):
self.assertRaises(TypeError, codecs.register)
self.assertRaises(TypeError, codecs.register, 42)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AttributeError: module '_winapi' has no attribute 'GetACP'")
def test_unregister(self):
name = "nonexistent_codec_name"
search_function = mock.Mock()
@@ -1859,28 +1857,23 @@ class CodecsModuleTest(unittest.TestCase):
self.assertRaises(LookupError, codecs.lookup, name)
search_function.assert_not_called()
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_lookup(self):
self.assertRaises(TypeError, codecs.lookup)
self.assertRaises(LookupError, codecs.lookup, "__spam__")
self.assertRaises(LookupError, codecs.lookup, " ")
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_getencoder(self):
self.assertRaises(TypeError, codecs.getencoder)
self.assertRaises(LookupError, codecs.getencoder, "__spam__")
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_getdecoder(self):
self.assertRaises(TypeError, codecs.getdecoder)
self.assertRaises(LookupError, codecs.getdecoder, "__spam__")
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_getreader(self):
self.assertRaises(TypeError, codecs.getreader)
self.assertRaises(LookupError, codecs.getreader, "__spam__")
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_getwriter(self):
self.assertRaises(TypeError, codecs.getwriter)
self.assertRaises(LookupError, codecs.getwriter, "__spam__")
@@ -1939,7 +1932,6 @@ class CodecsModuleTest(unittest.TestCase):
self.assertRaises(UnicodeError,
codecs.decode, b'abc', 'undefined', errors)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_file_closes_if_lookup_error_raised(self):
mock_open = mock.mock_open()
with mock.patch('builtins.open', mock_open) as file:
@@ -3287,7 +3279,6 @@ class ExceptionNotesTest(unittest.TestCase):
self.check_note(RuntimeError('a', 'b', 'c'), msg_re)
# http://bugs.python.org/issue19609
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_codec_lookup_failure(self):
msg = "^unknown encoding: {}$".format(self.codec_name)
with self.assertRaisesRegex(LookupError, msg):
@@ -3523,8 +3514,6 @@ class CodePageTest(unittest.TestCase):
False)
self.assertEqual(decoded, ('abc', 3))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_mbcs_alias(self):
# Check that looking up our 'default' codepage will return
# mbcs when we don't have a more specific one available

30
Lib/test/test_enum.py vendored
View File

@@ -1369,10 +1369,12 @@ class TestSpecial(unittest.TestCase):
[Outer.a, Outer.b, Outer.Inner],
)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipIf(
python_version < (3, 13),
'inner classes are still members',
)
python_version < (3, 13),
'inner classes are still members',
)
def test_nested_classes_in_enum_are_not_members(self):
"""Support locally-defined nested classes."""
class Outer(Enum):
@@ -4555,20 +4557,24 @@ class TestInternals(unittest.TestCase):
self.assertEqual(Color.green.value, 3)
self.assertEqual(Color.yellow.value, 4)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipIf(
python_version < (3, 13),
'mixed types with auto() will raise in 3.13',
)
python_version < (3, 13),
'inner classes are still members',
)
def test_auto_garbage_fail(self):
with self.assertRaisesRegex(TypeError, 'will require all values to be sortable'):
class Color(Enum):
red = 'red'
blue = auto()
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipIf(
python_version < (3, 13),
'mixed types with auto() will raise in 3.13',
)
python_version < (3, 13),
'inner classes are still members',
)
def test_auto_garbage_corrected_fail(self):
with self.assertRaisesRegex(TypeError, 'will require all values to be sortable'):
class Color(Enum):
@@ -4598,9 +4604,9 @@ class TestInternals(unittest.TestCase):
self.assertEqual(Color.blue.value, 'blue')
@unittest.skipIf(
python_version < (3, 13),
'auto() will return highest value + 1 in 3.13',
)
python_version < (3, 13),
'inner classes are still members',
)
def test_auto_with_aliases(self):
class Color(Enum):
red = auto()

View File

@@ -7,6 +7,7 @@ import _imp
import contextlib
import marshal
import os.path
import sys
import types
import unittest
import warnings
@@ -77,6 +78,7 @@ class ExecModuleTests(abc.LoaderTests):
self.assertTrue(hasattr(module, '__spec__'))
self.assertEqual(module.__spec__.loader_state.origname, name)
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON")
def test_package(self):
name = '__phello__'
module, output = self.exec_module(name)
@@ -90,6 +92,7 @@ class ExecModuleTests(abc.LoaderTests):
self.assertEqual(output, 'Hello world!\n')
self.assertEqual(module.__spec__.loader_state.origname, name)
@unittest.skipIf(sys.platform == 'win32', "TODO:RUSTPYTHON Flaky on Windows")
def test_lacking_parent(self):
name = '__phello__.spam'
with util.uncache('__phello__'):
@@ -147,6 +150,7 @@ class InspectLoaderTests:
result = self.machinery.FrozenImporter.get_source('__hello__')
self.assertIsNone(result)
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON")
def test_is_package(self):
# Should be able to tell what is a package.
test_for = (('__hello__', False), ('__phello__', True),

View File

@@ -52,6 +52,10 @@ class OpenDiskTests(FilesTests, unittest.TestCase):
def setUp(self):
self.data = data01
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_read_bytes(self):
super().test_read_bytes()
class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
pass
@@ -63,6 +67,9 @@ class OpenNamespaceTests(FilesTests, unittest.TestCase):
self.data = namespacedata01
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_read_bytes(self):
super().test_read_bytes()
class SiteDir:
def setUp(self):

View File

@@ -168,7 +168,6 @@ class FinderTests(abc.FinderTests):
found = self._find(finder, 'doesnotexist')
self.assertEqual(found, self.NOT_FOUND)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_ignore_file(self):
# If a directory got changed to a file from underneath us, then don't
# worry about looking for submodules.

402
Lib/test/test_math.py vendored
View File

@@ -4,6 +4,7 @@
from test.support import verbose, requires_IEEE_754
from test import support
import unittest
import fractions
import itertools
import decimal
import math
@@ -186,6 +187,9 @@ def result_check(expected, got, ulp_tol=5, abs_tol=0.0):
# Check exactly equal (applies also to strings representing exceptions)
if got == expected:
if not got and not expected:
if math.copysign(1, got) != math.copysign(1, expected):
return f"expected {expected}, got {got} (zero has wrong sign)"
return None
failure = "not equal"
@@ -234,6 +238,10 @@ class MyIndexable(object):
def __index__(self):
return self.value
class BadDescr:
def __get__(self, obj, objtype=None):
raise ValueError
class MathTests(unittest.TestCase):
def ftest(self, name, got, expected, ulp_tol=5, abs_tol=0.0):
@@ -323,6 +331,7 @@ class MathTests(unittest.TestCase):
self.ftest('atan2(0, 1)', math.atan2(0, 1), 0)
self.ftest('atan2(1, 1)', math.atan2(1, 1), math.pi/4)
self.ftest('atan2(1, 0)', math.atan2(1, 0), math.pi/2)
self.ftest('atan2(1, -1)', math.atan2(1, -1), 3*math.pi/4)
# math.atan2(0, x)
self.ftest('atan2(0., -inf)', math.atan2(0., NINF), math.pi)
@@ -416,16 +425,22 @@ class MathTests(unittest.TestCase):
return 42
class TestNoCeil:
pass
class TestBadCeil:
__ceil__ = BadDescr()
self.assertEqual(math.ceil(TestCeil()), 42)
self.assertEqual(math.ceil(FloatCeil()), 42)
self.assertEqual(math.ceil(FloatLike(42.5)), 43)
self.assertRaises(TypeError, math.ceil, TestNoCeil())
self.assertRaises(ValueError, math.ceil, TestBadCeil())
t = TestNoCeil()
t.__ceil__ = lambda *args: args
self.assertRaises(TypeError, math.ceil, t)
self.assertRaises(TypeError, math.ceil, t, 0)
self.assertEqual(math.ceil(FloatLike(+1.0)), +1.0)
self.assertEqual(math.ceil(FloatLike(-1.0)), -1.0)
@requires_IEEE_754
def testCopysign(self):
self.assertEqual(math.copysign(1, 42), 1.0)
@@ -566,16 +581,22 @@ class MathTests(unittest.TestCase):
return 42
class TestNoFloor:
pass
class TestBadFloor:
__floor__ = BadDescr()
self.assertEqual(math.floor(TestFloor()), 42)
self.assertEqual(math.floor(FloatFloor()), 42)
self.assertEqual(math.floor(FloatLike(41.9)), 41)
self.assertRaises(TypeError, math.floor, TestNoFloor())
self.assertRaises(ValueError, math.floor, TestBadFloor())
t = TestNoFloor()
t.__floor__ = lambda *args: args
self.assertRaises(TypeError, math.floor, t)
self.assertRaises(TypeError, math.floor, t, 0)
self.assertEqual(math.floor(FloatLike(+1.0)), +1.0)
self.assertEqual(math.floor(FloatLike(-1.0)), -1.0)
def testFmod(self):
self.assertRaises(TypeError, math.fmod)
self.ftest('fmod(10, 1)', math.fmod(10, 1), 0.0)
@@ -597,6 +618,7 @@ class MathTests(unittest.TestCase):
self.assertEqual(math.fmod(-3.0, NINF), -3.0)
self.assertEqual(math.fmod(0.0, 3.0), 0.0)
self.assertEqual(math.fmod(0.0, NINF), 0.0)
self.assertRaises(ValueError, math.fmod, INF, INF)
def testFrexp(self):
self.assertRaises(TypeError, math.frexp)
@@ -638,7 +660,7 @@ class MathTests(unittest.TestCase):
def msum(iterable):
"""Full precision summation. Compute sum(iterable) without any
intermediate accumulation of error. Based on the 'lsum' function
at http://code.activestate.com/recipes/393090/
at https://code.activestate.com/recipes/393090-binary-floating-point-summation-accurate-to-full-p/
"""
tmant, texp = 0, 0
@@ -666,6 +688,7 @@ class MathTests(unittest.TestCase):
([], 0.0),
([0.0], 0.0),
([1e100, 1.0, -1e100, 1e-100, 1e50, -1.0, -1e50], 1e-100),
([1e100, 1.0, -1e100, 1e-100, 1e50, -1, -1e50], 1e-100),
([2.0**53, -0.5, -2.0**-54], 2.0**53-1.0),
([2.0**53, 1.0, 2.0**-100], 2.0**53+2.0),
([2.0**53+10.0, 1.0, 2.0**-100], 2.0**53+12.0),
@@ -713,6 +736,22 @@ class MathTests(unittest.TestCase):
s = msum(vals)
self.assertEqual(msum(vals), math.fsum(vals))
self.assertEqual(math.fsum([1.0, math.inf]), math.inf)
self.assertTrue(math.isnan(math.fsum([math.nan, 1.0])))
self.assertEqual(math.fsum([1e100, FloatLike(1.0), -1e100, 1e-100,
1e50, FloatLike(-1.0), -1e50]), 1e-100)
self.assertRaises(OverflowError, math.fsum, [1e+308, 1e+308])
self.assertRaises(ValueError, math.fsum, [math.inf, -math.inf])
self.assertRaises(TypeError, math.fsum, ['spam'])
self.assertRaises(TypeError, math.fsum, 1)
self.assertRaises(OverflowError, math.fsum, [10**1000])
def bad_iter():
yield 1.0
raise ZeroDivisionError
self.assertRaises(ZeroDivisionError, math.fsum, bad_iter())
def testGcd(self):
gcd = math.gcd
self.assertEqual(gcd(0, 0), 0)
@@ -773,9 +812,13 @@ class MathTests(unittest.TestCase):
# Test allowable types (those with __float__)
self.assertEqual(hypot(12.0, 5.0), 13.0)
self.assertEqual(hypot(12, 5), 13)
self.assertEqual(hypot(0.75, -1), 1.25)
self.assertEqual(hypot(-1, 0.75), 1.25)
self.assertEqual(hypot(0.75, FloatLike(-1.)), 1.25)
self.assertEqual(hypot(FloatLike(-1.), 0.75), 1.25)
self.assertEqual(hypot(Decimal(12), Decimal(5)), 13)
self.assertEqual(hypot(Fraction(12, 32), Fraction(5, 32)), Fraction(13, 32))
self.assertEqual(hypot(bool(1), bool(0), bool(1), bool(1)), math.sqrt(3))
self.assertEqual(hypot(True, False, True, True, True), 2.0)
# Test corner cases
self.assertEqual(hypot(0.0, 0.0), 0.0) # Max input is zero
@@ -830,6 +873,8 @@ class MathTests(unittest.TestCase):
scale = FLOAT_MIN / 2.0 ** exp
self.assertEqual(math.hypot(4*scale, 3*scale), 5*scale)
self.assertRaises(TypeError, math.hypot, *([1.0]*18), 'spam')
@requires_IEEE_754
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
"hypot() loses accuracy on machines with double rounding")
@@ -922,12 +967,16 @@ class MathTests(unittest.TestCase):
# Test allowable types (those with __float__)
self.assertEqual(dist((14.0, 1.0), (2.0, -4.0)), 13.0)
self.assertEqual(dist((14, 1), (2, -4)), 13)
self.assertEqual(dist((FloatLike(14.), 1), (2, -4)), 13)
self.assertEqual(dist((11, 1), (FloatLike(-1.), -4)), 13)
self.assertEqual(dist((14, FloatLike(-1.)), (2, -6)), 13)
self.assertEqual(dist((14, -1), (2, -6)), 13)
self.assertEqual(dist((D(14), D(1)), (D(2), D(-4))), D(13))
self.assertEqual(dist((F(14, 32), F(1, 32)), (F(2, 32), F(-4, 32))),
F(13, 32))
self.assertEqual(dist((True, True, False, True, False),
(True, False, True, True, False)),
sqrt(2.0))
self.assertEqual(dist((True, True, False, False, True, True),
(True, False, True, False, False, False)),
2.0)
# Test corner cases
self.assertEqual(dist((13.25, 12.5, -3.25),
@@ -965,6 +1014,8 @@ class MathTests(unittest.TestCase):
dist((1, 2, 3, 4), (5, 6, 7))
with self.assertRaises(ValueError): # Check dimension agree
dist((1, 2, 3), (4, 5, 6, 7))
with self.assertRaises(TypeError):
dist((1,)*17 + ("spam",), (1,)*18)
with self.assertRaises(TypeError): # Rejects invalid types
dist("abc", "xyz")
int_too_big_for_float = 10 ** (sys.float_info.max_10_exp + 5)
@@ -972,6 +1023,16 @@ class MathTests(unittest.TestCase):
dist((1, int_too_big_for_float), (2, 3))
with self.assertRaises((ValueError, OverflowError)):
dist((2, 3), (1, int_too_big_for_float))
with self.assertRaises(TypeError):
dist((1,), 2)
with self.assertRaises(TypeError):
dist([1], 2)
class BadFloat:
__float__ = BadDescr()
with self.assertRaises(ValueError):
dist([1], [BadFloat()])
# Verify that the one dimensional case is equivalent to abs()
for i in range(20):
@@ -1110,6 +1171,7 @@ class MathTests(unittest.TestCase):
def testLdexp(self):
self.assertRaises(TypeError, math.ldexp)
self.assertRaises(TypeError, math.ldexp, 2.0, 1.1)
self.ftest('ldexp(0,1)', math.ldexp(0,1), 0)
self.ftest('ldexp(1,1)', math.ldexp(1,1), 2)
self.ftest('ldexp(1,-1)', math.ldexp(1,-1), 0.5)
@@ -1142,6 +1204,7 @@ class MathTests(unittest.TestCase):
def testLog(self):
self.assertRaises(TypeError, math.log)
self.assertRaises(TypeError, math.log, 1, 2, 3)
self.ftest('log(1/e)', math.log(1/math.e), -1)
self.ftest('log(1)', math.log(1), 0)
self.ftest('log(e)', math.log(math.e), 1)
@@ -1152,6 +1215,7 @@ class MathTests(unittest.TestCase):
2302.5850929940457)
self.assertRaises(ValueError, math.log, -1.5)
self.assertRaises(ValueError, math.log, -10**1000)
self.assertRaises(ValueError, math.log, 10, -10)
self.assertRaises(ValueError, math.log, NINF)
self.assertEqual(math.log(INF), INF)
self.assertTrue(math.isnan(math.log(NAN)))
@@ -1202,6 +1266,277 @@ class MathTests(unittest.TestCase):
self.assertEqual(math.log(INF), INF)
self.assertTrue(math.isnan(math.log10(NAN)))
def testSumProd(self):
sumprod = math.sumprod
Decimal = decimal.Decimal
Fraction = fractions.Fraction
# Core functionality
self.assertEqual(sumprod(iter([10, 20, 30]), (1, 2, 3)), 140)
self.assertEqual(sumprod([1.5, 2.5], [3.5, 4.5]), 16.5)
self.assertEqual(sumprod([], []), 0)
self.assertEqual(sumprod([-1], [1.]), -1)
self.assertEqual(sumprod([1.], [-1]), -1)
# Type preservation and coercion
for v in [
(10, 20, 30),
(1.5, -2.5),
(Fraction(3, 5), Fraction(4, 5)),
(Decimal(3.5), Decimal(4.5)),
(2.5, 10), # float/int
(2.5, Fraction(3, 5)), # float/fraction
(25, Fraction(3, 5)), # int/fraction
(25, Decimal(4.5)), # int/decimal
]:
for p, q in [(v, v), (v, v[::-1])]:
with self.subTest(p=p, q=q):
expected = sum(p_i * q_i for p_i, q_i in zip(p, q, strict=True))
actual = sumprod(p, q)
self.assertEqual(expected, actual)
self.assertEqual(type(expected), type(actual))
# Bad arguments
self.assertRaises(TypeError, sumprod) # No args
self.assertRaises(TypeError, sumprod, []) # One arg
self.assertRaises(TypeError, sumprod, [], [], []) # Three args
self.assertRaises(TypeError, sumprod, None, [10]) # Non-iterable
self.assertRaises(TypeError, sumprod, [10], None) # Non-iterable
self.assertRaises(TypeError, sumprod, ['x'], [1.0])
# Uneven lengths
self.assertRaises(ValueError, sumprod, [10, 20], [30])
self.assertRaises(ValueError, sumprod, [10], [20, 30])
# Overflows
self.assertEqual(sumprod([10**20], [1]), 10**20)
self.assertEqual(sumprod([1], [10**20]), 10**20)
self.assertEqual(sumprod([10**10], [10**10]), 10**20)
self.assertEqual(sumprod([10**7]*10**5, [10**7]*10**5), 10**19)
self.assertRaises(OverflowError, sumprod, [10**1000], [1.0])
self.assertRaises(OverflowError, sumprod, [1.0], [10**1000])
# Error in iterator
def raise_after(n):
for i in range(n):
yield i
raise RuntimeError
with self.assertRaises(RuntimeError):
sumprod(range(10), raise_after(5))
with self.assertRaises(RuntimeError):
sumprod(raise_after(5), range(10))
from test.test_iter import BasicIterClass
self.assertEqual(sumprod(BasicIterClass(1), [1]), 0)
self.assertEqual(sumprod([1], BasicIterClass(1)), 0)
# Error in multiplication
class BadMultiply:
def __mul__(self, other):
raise RuntimeError
def __rmul__(self, other):
raise RuntimeError
with self.assertRaises(RuntimeError):
sumprod([10, BadMultiply(), 30], [1, 2, 3])
with self.assertRaises(RuntimeError):
sumprod([1, 2, 3], [10, BadMultiply(), 30])
# Error in addition
with self.assertRaises(TypeError):
sumprod(['abc', 3], [5, 10])
with self.assertRaises(TypeError):
sumprod([5, 10], ['abc', 3])
# Special values should give the same as the pure python recipe
self.assertEqual(sumprod([10.1, math.inf], [20.2, 30.3]), math.inf)
self.assertEqual(sumprod([10.1, math.inf], [math.inf, 30.3]), math.inf)
self.assertEqual(sumprod([10.1, math.inf], [math.inf, math.inf]), math.inf)
self.assertEqual(sumprod([10.1, -math.inf], [20.2, 30.3]), -math.inf)
self.assertTrue(math.isnan(sumprod([10.1, math.inf], [-math.inf, math.inf])))
self.assertTrue(math.isnan(sumprod([10.1, math.nan], [20.2, 30.3])))
self.assertTrue(math.isnan(sumprod([10.1, math.inf], [math.nan, 30.3])))
self.assertTrue(math.isnan(sumprod([10.1, math.inf], [20.3, math.nan])))
# Error cases that arose during development
args = ((-5, -5, 10), (1.5, 4611686018427387904, 2305843009213693952))
self.assertEqual(sumprod(*args), 0.0)
@requires_IEEE_754
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
"sumprod() accuracy not guaranteed on machines with double rounding")
@support.cpython_only # Other implementations may choose a different algorithm
def test_sumprod_accuracy(self):
sumprod = math.sumprod
self.assertEqual(sumprod([0.1] * 10, [1]*10), 1.0)
self.assertEqual(sumprod([0.1] * 20, [True, False] * 10), 1.0)
self.assertEqual(sumprod([True, False] * 10, [0.1] * 20), 1.0)
self.assertEqual(sumprod([1.0, 10E100, 1.0, -10E100], [1.0]*4), 2.0)
@support.requires_resource('cpu')
def test_sumprod_stress(self):
sumprod = math.sumprod
product = itertools.product
Decimal = decimal.Decimal
Fraction = fractions.Fraction
class Int(int):
def __add__(self, other):
return Int(int(self) + int(other))
def __mul__(self, other):
return Int(int(self) * int(other))
__radd__ = __add__
__rmul__ = __mul__
def __repr__(self):
return f'Int({int(self)})'
class Flt(float):
def __add__(self, other):
return Int(int(self) + int(other))
def __mul__(self, other):
return Int(int(self) * int(other))
__radd__ = __add__
__rmul__ = __mul__
def __repr__(self):
return f'Flt({int(self)})'
def baseline_sumprod(p, q):
"""This defines the target behavior including exceptions and special values.
However, it is subject to rounding errors, so float inputs should be exactly
representable with only a few bits.
"""
total = 0
for p_i, q_i in zip(p, q, strict=True):
total += p_i * q_i
return total
def run(func, *args):
"Make comparing functions easier. Returns error status, type, and result."
try:
result = func(*args)
except (AssertionError, NameError):
raise
except Exception as e:
return type(e), None, 'None'
return None, type(result), repr(result)
pools = [
(-5, 10, -2**20, 2**31, 2**40, 2**61, 2**62, 2**80, 1.5, Int(7)),
(5.25, -3.5, 4.75, 11.25, 400.5, 0.046875, 0.25, -1.0, -0.078125),
(-19.0*2**500, 11*2**1000, -3*2**1500, 17*2*333,
5.25, -3.25, -3.0*2**(-333), 3, 2**513),
(3.75, 2.5, -1.5, float('inf'), -float('inf'), float('NaN'), 14,
9, 3+4j, Flt(13), 0.0),
(13.25, -4.25, Decimal('10.5'), Decimal('-2.25'), Fraction(13, 8),
Fraction(-11, 16), 4.75 + 0.125j, 97, -41, Int(3)),
(Decimal('6.125'), Decimal('12.375'), Decimal('-2.75'), Decimal(0),
Decimal('Inf'), -Decimal('Inf'), Decimal('NaN'), 12, 13.5),
(-2.0 ** -1000, 11*2**1000, 3, 7, -37*2**32, -2*2**-537, -2*2**-538,
2*2**-513),
(-7 * 2.0 ** -510, 5 * 2.0 ** -520, 17, -19.0, -6.25),
(11.25, -3.75, -0.625, 23.375, True, False, 7, Int(5)),
]
for pool in pools:
for size in range(4):
for args1 in product(pool, repeat=size):
for args2 in product(pool, repeat=size):
args = (args1, args2)
self.assertEqual(
run(baseline_sumprod, *args),
run(sumprod, *args),
args,
)
@requires_IEEE_754
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
"sumprod() accuracy not guaranteed on machines with double rounding")
@support.cpython_only # Other implementations may choose a different algorithm
@support.requires_resource('cpu')
def test_sumprod_extended_precision_accuracy(self):
import operator
from fractions import Fraction
from itertools import starmap
from collections import namedtuple
from math import log2, exp2, fabs
from random import choices, uniform, shuffle
from statistics import median
DotExample = namedtuple('DotExample', ('x', 'y', 'target_sumprod', 'condition'))
def DotExact(x, y):
vec1 = map(Fraction, x)
vec2 = map(Fraction, y)
return sum(starmap(operator.mul, zip(vec1, vec2, strict=True)))
def Condition(x, y):
return 2.0 * DotExact(map(abs, x), map(abs, y)) / abs(DotExact(x, y))
def linspace(lo, hi, n):
width = (hi - lo) / (n - 1)
return [lo + width * i for i in range(n)]
def GenDot(n, c):
""" Algorithm 6.1 (GenDot) works as follows. The condition number (5.7) of
the dot product xT y is proportional to the degree of cancellation. In
order to achieve a prescribed cancellation, we generate the first half of
the vectors x and y randomly within a large exponent range. This range is
chosen according to the anticipated condition number. The second half of x
and y is then constructed choosing xi randomly with decreasing exponent,
and calculating yi such that some cancellation occurs. Finally, we permute
the vectors x, y randomly and calculate the achieved condition number.
"""
assert n >= 6
n2 = n // 2
x = [0.0] * n
y = [0.0] * n
b = log2(c)
# First half with exponents from 0 to |_b/2_| and random ints in between
e = choices(range(int(b/2)), k=n2)
e[0] = int(b / 2) + 1
e[-1] = 0.0
x[:n2] = [uniform(-1.0, 1.0) * exp2(p) for p in e]
y[:n2] = [uniform(-1.0, 1.0) * exp2(p) for p in e]
# Second half
e = list(map(round, linspace(b/2, 0.0 , n-n2)))
for i in range(n2, n):
x[i] = uniform(-1.0, 1.0) * exp2(e[i - n2])
y[i] = (uniform(-1.0, 1.0) * exp2(e[i - n2]) - DotExact(x, y)) / x[i]
# Shuffle
pairs = list(zip(x, y))
shuffle(pairs)
x, y = zip(*pairs)
return DotExample(x, y, DotExact(x, y), Condition(x, y))
def RelativeError(res, ex):
x, y, target_sumprod, condition = ex
n = DotExact(list(x) + [-res], list(y) + [1])
return fabs(n / target_sumprod)
def Trial(dotfunc, c, n):
ex = GenDot(10, c)
res = dotfunc(ex.x, ex.y)
return RelativeError(res, ex)
times = 1000 # Number of trials
n = 20 # Length of vectors
c = 1e30 # Target condition number
# If the following test fails, it means that the C math library
# implementation of fma() is not compliant with the C99 standard
# and is inaccurate. To solve this problem, make a new build
# with the symbol UNRELIABLE_FMA defined. That will enable a
# slower but accurate code path that avoids the fma() call.
relative_err = median(Trial(math.sumprod, c, n) for i in range(times))
self.assertLess(relative_err, 1e-16)
def testModf(self):
self.assertRaises(TypeError, math.modf)
@@ -1235,6 +1570,7 @@ class MathTests(unittest.TestCase):
self.assertTrue(math.isnan(math.pow(2, NAN)))
self.assertTrue(math.isnan(math.pow(0, NAN)))
self.assertEqual(math.pow(1, NAN), 1)
self.assertRaises(OverflowError, math.pow, 1e+100, 1e+100)
# pow(0., x)
self.assertEqual(math.pow(0., INF), 0.)
@@ -1550,7 +1886,7 @@ class MathTests(unittest.TestCase):
try:
self.assertTrue(math.isnan(math.tan(INF)))
self.assertTrue(math.isnan(math.tan(NINF)))
except:
except ValueError:
self.assertRaises(ValueError, math.tan, INF)
self.assertRaises(ValueError, math.tan, NINF)
self.assertTrue(math.isnan(math.tan(NAN)))
@@ -1591,6 +1927,8 @@ class MathTests(unittest.TestCase):
return 23
class TestNoTrunc:
pass
class TestBadTrunc:
__trunc__ = BadDescr()
self.assertEqual(math.trunc(TestTrunc()), 23)
self.assertEqual(math.trunc(FloatTrunc()), 23)
@@ -1599,6 +1937,7 @@ class MathTests(unittest.TestCase):
self.assertRaises(TypeError, math.trunc, 1, 2)
self.assertRaises(TypeError, math.trunc, FloatLike(23.5))
self.assertRaises(TypeError, math.trunc, TestNoTrunc())
self.assertRaises(ValueError, math.trunc, TestBadTrunc())
def testIsfinite(self):
self.assertTrue(math.isfinite(0.0))
@@ -1626,11 +1965,11 @@ class MathTests(unittest.TestCase):
self.assertFalse(math.isinf(0.))
self.assertFalse(math.isinf(1.))
@requires_IEEE_754
def test_nan_constant(self):
# `math.nan` must be a quiet NaN with positive sign bit
self.assertTrue(math.isnan(math.nan))
self.assertEqual(math.copysign(1., math.nan), 1.)
@requires_IEEE_754
def test_inf_constant(self):
self.assertTrue(math.isinf(math.inf))
self.assertGreater(math.inf, 0.0)
@@ -1719,6 +2058,13 @@ class MathTests(unittest.TestCase):
except OverflowError:
result = 'OverflowError'
# C99+ says for math.h's sqrt: If the argument is +∞ or ±0, it is
# returned, unmodified. On another hand, for csqrt: If z is ±0+0i,
# the result is +0+0i. Lets correct zero sign of er to follow
# first convention.
if id in ['sqrt0002', 'sqrt0003', 'sqrt1001', 'sqrt1023']:
er = math.copysign(er, ar)
# Default tolerances
ulp_tol, abs_tol = 5, 0.0
@@ -1802,6 +2148,8 @@ class MathTests(unittest.TestCase):
'\n '.join(failures))
def test_prod(self):
from fractions import Fraction as F
prod = math.prod
self.assertEqual(prod([]), 1)
self.assertEqual(prod([], start=5), 5)
@@ -1813,6 +2161,14 @@ class MathTests(unittest.TestCase):
self.assertEqual(prod([1.0, 2.0, 3.0, 4.0, 5.0]), 120.0)
self.assertEqual(prod([1, 2, 3, 4.0, 5.0]), 120.0)
self.assertEqual(prod([1.0, 2.0, 3.0, 4, 5]), 120.0)
self.assertEqual(prod([1., F(3, 2)]), 1.5)
# Error in multiplication
class BadMultiply:
def __rmul__(self, other):
raise RuntimeError
with self.assertRaises(RuntimeError):
prod([10., BadMultiply()])
# Test overflow in fast-path for integers
self.assertEqual(prod([1, 1, 2**32, 1, 1]), 2**32)
@@ -2044,11 +2400,20 @@ class MathTests(unittest.TestCase):
float.fromhex('0x1.fffffffffffffp-1'))
self.assertEqual(math.nextafter(1.0, INF),
float.fromhex('0x1.0000000000001p+0'))
self.assertEqual(math.nextafter(1.0, -INF, steps=1),
float.fromhex('0x1.fffffffffffffp-1'))
self.assertEqual(math.nextafter(1.0, INF, steps=1),
float.fromhex('0x1.0000000000001p+0'))
self.assertEqual(math.nextafter(1.0, -INF, steps=3),
float.fromhex('0x1.ffffffffffffdp-1'))
self.assertEqual(math.nextafter(1.0, INF, steps=3),
float.fromhex('0x1.0000000000003p+0'))
# x == y: y is returned
self.assertEqual(math.nextafter(2.0, 2.0), 2.0)
self.assertEqualSign(math.nextafter(-0.0, +0.0), +0.0)
self.assertEqualSign(math.nextafter(+0.0, -0.0), -0.0)
for steps in range(1, 5):
self.assertEqual(math.nextafter(2.0, 2.0, steps=steps), 2.0)
self.assertEqualSign(math.nextafter(-0.0, +0.0, steps=steps), +0.0)
self.assertEqualSign(math.nextafter(+0.0, -0.0, steps=steps), -0.0)
# around 0.0
smallest_subnormal = sys.float_info.min * sys.float_info.epsilon
@@ -2073,6 +2438,11 @@ class MathTests(unittest.TestCase):
self.assertIsNaN(math.nextafter(1.0, NAN))
self.assertIsNaN(math.nextafter(NAN, NAN))
self.assertEqual(1.0, math.nextafter(1.0, INF, steps=0))
with self.assertRaises(ValueError):
math.nextafter(1.0, INF, steps=-1)
@requires_IEEE_754
def test_ulp(self):
self.assertEqual(math.ulp(1.0), sys.float_info.epsilon)
@@ -2112,6 +2482,14 @@ class MathTests(unittest.TestCase):
# argument to a float.
self.assertFalse(getattr(y, "converted", False))
def test_input_exceptions(self):
self.assertRaises(TypeError, math.exp, "spam")
self.assertRaises(TypeError, math.erf, "spam")
self.assertRaises(TypeError, math.atan2, "spam", 1.0)
self.assertRaises(TypeError, math.atan2, 1.0, "spam")
self.assertRaises(TypeError, math.atan2, 1.0)
self.assertRaises(TypeError, math.atan2, 1.0, 2.0, 3.0)
# Custom assertions.
def assertIsNaN(self, value):
@@ -2252,7 +2630,7 @@ class IsCloseTests(unittest.TestCase):
def load_tests(loader, tests, pattern):
from doctest import DocFileSuite
# tests.addTest(DocFileSuite("ieee754.txt"))
tests.addTest(DocFileSuite("ieee754.txt"))
return tests
if __name__ == '__main__':

View File

@@ -1012,8 +1012,6 @@ def gamma(z, sqrt2pi=(2.0*pi)**0.5):
])
class TestDistributions(unittest.TestCase):
# TODO: RUSTPYTHON ValueError: math domain error
@unittest.expectedFailure
def test_zeroinputs(self):
# Verify that distributions can handle a series of zero inputs'
g = random.Random()

View File

@@ -1237,7 +1237,6 @@ class TestDetectEncoding(TestCase):
found, consumed_lines = detect_encoding(rl)
self.assertEqual(found, "utf-8")
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_short_files(self):
readline = self.get_readline((b'print(something)\n',))
encoding, consumed_lines = detect_encoding(readline)
@@ -1316,7 +1315,6 @@ class TestDetectEncoding(TestCase):
ins = Bunk(lines, path)
detect_encoding(ins.readline)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_open_error(self):
# Issue #23840: open() must close the binary file on error
m = BytesIO(b'#coding:xxx')

View File

@@ -2,7 +2,7 @@
# [RustPython](https://rustpython.github.io/)
A Python-3 (CPython >= 3.12.0) Interpreter written in Rust :snake: :scream:
A Python-3 (CPython >= 3.13.0) Interpreter written in Rust :snake: :scream:
:metal:.
[![Build Status](https://github.com/RustPython/RustPython/workflows/CI/badge.svg)](https://github.com/RustPython/RustPython/actions?query=workflow%3ACI)

View File

@@ -133,6 +133,69 @@ pub fn nextafter(x: f64, y: f64) -> f64 {
}
}
#[allow(clippy::float_cmp)]
pub fn nextafter_with_steps(x: f64, y: f64, steps: u64) -> f64 {
if x == y {
y
} else if x.is_nan() || y.is_nan() {
f64::NAN
} else if x >= f64::INFINITY {
f64::MAX
} else if x <= f64::NEG_INFINITY {
f64::MIN
} else if x == 0.0 {
f64::from_bits(1).copysign(y)
} else {
if steps == 0 {
return x;
}
if x.is_nan() {
return x;
}
if y.is_nan() {
return y;
}
let sign_bit: u64 = 1 << 63;
let mut ux = x.to_bits();
let uy = y.to_bits();
let ax = ux & !sign_bit;
let ay = uy & !sign_bit;
// If signs are different
if ((ux ^ uy) & sign_bit) != 0 {
return if ax + ay <= steps {
f64::from_bits(uy)
} else if ax < steps {
let result = (uy & sign_bit) | (steps - ax);
f64::from_bits(result)
} else {
ux -= steps;
f64::from_bits(ux)
};
}
// If signs are the same
if ax > ay {
if ax - ay >= steps {
ux -= steps;
f64::from_bits(ux)
} else {
f64::from_bits(uy)
}
} else if ay - ax >= steps {
ux += steps;
f64::from_bits(ux)
} else {
f64::from_bits(uy)
}
}
}
pub fn ulp(x: f64) -> f64 {
if x.is_nan() {
return x;

View File

@@ -37,11 +37,11 @@ impl BuildHasher for HashSecret {
}
}
impl rand::distributions::Distribution<HashSecret> for rand::distributions::Standard {
impl rand::distr::Distribution<HashSecret> for rand::distr::StandardUniform {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> HashSecret {
HashSecret {
k0: rng.gen(),
k1: rng.gen(),
k0: rng.random(),
k1: rng.random(),
}
}
}
@@ -114,7 +114,7 @@ pub fn hash_float(value: f64) -> Option<PyHash> {
let mut e = frexp.1;
let mut x: PyUHash = 0;
while m != 0.0 {
x = ((x << 28) & MODULUS) | x >> (BITS - 28);
x = ((x << 28) & MODULUS) | (x >> (BITS - 28));
m *= 268_435_456.0; // 2**28
e -= 28;
let y = m as PyUHash; // pull out integer part
@@ -132,7 +132,7 @@ pub fn hash_float(value: f64) -> Option<PyHash> {
} else {
BITS32 - 1 - ((-1 - e) % BITS32)
};
x = ((x << e) & MODULUS) | x >> (BITS32 - e);
x = ((x << e) & MODULUS) | (x >> (BITS32 - e));
Some(fix_sentinel(x as PyHash * value.signum() as PyHash))
}

View File

@@ -322,6 +322,37 @@ pub mod levenshtein {
}
}
/// Replace all tabs in a string with spaces, using the given tab size.
pub fn expandtabs(input: &str, tab_size: usize) -> String {
let tab_stop = tab_size;
let mut expanded_str = String::with_capacity(input.len());
let mut tab_size = tab_stop;
let mut col_count = 0usize;
for ch in input.chars() {
match ch {
'\t' => {
let num_spaces = tab_size - col_count;
col_count += num_spaces;
let expand = " ".repeat(num_spaces);
expanded_str.push_str(&expand);
}
'\r' | '\n' => {
expanded_str.push(ch);
col_count = 0;
tab_size = 0;
}
_ => {
expanded_str.push(ch);
col_count += 1;
}
}
if col_count >= tab_size {
tab_size += tab_stop;
}
}
expanded_str
}
/// Creates an [`AsciiStr`][ascii::AsciiStr] from a string literal, throwing a compile error if the
/// literal isn't actually ascii.
///

View File

@@ -11,6 +11,7 @@ license.workspace = true
[dependencies]
rustpython-ast = { workspace = true, features=["unparse", "constant-optimization"] }
rustpython-common = { workspace = true }
rustpython-parser-core = { workspace = true }
rustpython-compiler-core = { workspace = true }

View File

@@ -18,7 +18,10 @@ use num_complex::Complex64;
use num_traits::ToPrimitive;
use rustpython_ast::located::{self as located_ast, Located};
use rustpython_compiler_core::{
bytecode::{self, Arg as OpArgMarker, CodeObject, ConstantData, Instruction, OpArg, OpArgType},
bytecode::{
self, Arg as OpArgMarker, CodeObject, ComparisonOperator, ConstantData, Instruction, OpArg,
OpArgType,
},
Mode,
};
use rustpython_parser_core::source_code::{LineNumber, SourceLocation};
@@ -211,6 +214,12 @@ macro_rules! emit {
};
}
struct PatternContext {
current_block: usize,
blocks: Vec<ir::BlockIdx>,
allow_irrefutable: bool,
}
impl Compiler {
fn new(opts: CompileOpts, source_path: String, code_name: String) -> Self {
let module_code = ir::CodeInfo {
@@ -1086,8 +1095,27 @@ impl Compiler {
self.store_name(name.as_ref())?;
}
}
located_ast::TypeParam::ParamSpec(_) => todo!(),
located_ast::TypeParam::TypeVarTuple(_) => todo!(),
located_ast::TypeParam::ParamSpec(located_ast::TypeParamParamSpec {
name, ..
}) => {
self.emit_load_const(ConstantData::Str {
value: name.to_string(),
});
emit!(self, Instruction::ParamSpec);
emit!(self, Instruction::Duplicate);
self.store_name(name.as_ref())?;
}
located_ast::TypeParam::TypeVarTuple(located_ast::TypeParamTypeVarTuple {
name,
..
}) => {
self.emit_load_const(ConstantData::Str {
value: name.to_string(),
});
emit!(self, Instruction::TypeVarTuple);
emit!(self, Instruction::Duplicate);
self.store_name(name.as_ref())?;
}
};
}
emit!(
@@ -1755,14 +1783,152 @@ impl Compiler {
Ok(())
}
fn compile_pattern_value(
&mut self,
value: &located_ast::PatternMatchValue,
_pattern_context: &mut PatternContext,
) -> CompileResult<()> {
self.compile_expression(&value.value)?;
emit!(
self,
Instruction::CompareOperation {
op: ComparisonOperator::Equal
}
);
Ok(())
}
fn compile_pattern_as(
&mut self,
as_pattern: &located_ast::PatternMatchAs,
pattern_context: &mut PatternContext,
) -> CompileResult<()> {
if as_pattern.pattern.is_none() && !pattern_context.allow_irrefutable {
// TODO: better error message
if let Some(_name) = as_pattern.name.as_ref() {
return Err(
self.error_loc(CodegenErrorType::InvalidMatchCase, as_pattern.location())
);
}
return Err(self.error_loc(CodegenErrorType::InvalidMatchCase, as_pattern.location()));
}
// Need to make a copy for (possibly) storing later:
emit!(self, Instruction::Duplicate);
if let Some(pattern) = &as_pattern.pattern {
self.compile_pattern_inner(pattern, pattern_context)?;
}
if let Some(name) = as_pattern.name.as_ref() {
self.store_name(name.as_str())?;
} else {
emit!(self, Instruction::Pop);
}
Ok(())
}
fn compile_pattern_inner(
&mut self,
pattern_type: &located_ast::Pattern,
pattern_context: &mut PatternContext,
) -> CompileResult<()> {
match &pattern_type {
located_ast::Pattern::MatchValue(value) => {
self.compile_pattern_value(value, pattern_context)
}
located_ast::Pattern::MatchAs(as_pattern) => {
self.compile_pattern_as(as_pattern, pattern_context)
}
_ => {
eprintln!("not implemented pattern type: {pattern_type:?}");
Err(self.error(CodegenErrorType::NotImplementedYet))
}
}
}
fn compile_pattern(
&mut self,
pattern_type: &located_ast::Pattern,
pattern_context: &mut PatternContext,
) -> CompileResult<()> {
self.compile_pattern_inner(pattern_type, pattern_context)?;
emit!(
self,
Instruction::JumpIfFalse {
target: pattern_context.blocks[pattern_context.current_block + 1]
}
);
Ok(())
}
fn compile_match_inner(
&mut self,
subject: &located_ast::Expr,
cases: &[located_ast::MatchCase],
pattern_context: &mut PatternContext,
) -> CompileResult<()> {
self.compile_expression(subject)?;
pattern_context.blocks = std::iter::repeat_with(|| self.new_block())
.take(cases.len() + 1)
.collect::<Vec<_>>();
let end_block = *pattern_context.blocks.last().unwrap();
let _match_case_type = cases.last().expect("cases is not empty");
// TODO: get proper check for default case
// let has_default = match_case_type.pattern.is_match_as() && 1 < cases.len();
let has_default = false;
for i in 0..cases.len() - (has_default as usize) {
self.switch_to_block(pattern_context.blocks[i]);
pattern_context.current_block = i;
pattern_context.allow_irrefutable = cases[i].guard.is_some() || i == cases.len() - 1;
let m = &cases[i];
// Only copy the subject if we're *not* on the last case:
if i != cases.len() - has_default as usize - 1 {
emit!(self, Instruction::Duplicate);
}
self.compile_pattern(&m.pattern, pattern_context)?;
self.compile_statements(&m.body)?;
emit!(self, Instruction::Jump { target: end_block });
}
// TODO: below code is not called and does not work
if has_default {
// A trailing "case _" is common, and lets us save a bit of redundant
// pushing and popping in the loop above:
let m = &cases.last().unwrap();
self.switch_to_block(*pattern_context.blocks.last().unwrap());
if cases.len() == 1 {
// No matches. Done with the subject:
emit!(self, Instruction::Pop);
} else {
// Show line coverage for default case (it doesn't create bytecode)
// emit!(self, Instruction::Nop);
}
self.compile_statements(&m.body)?;
}
self.switch_to_block(end_block);
let code = self.current_code_info();
pattern_context
.blocks
.iter()
.zip(pattern_context.blocks.iter().skip(1))
.for_each(|(a, b)| {
code.blocks[a.0 as usize].next = *b;
});
Ok(())
}
fn compile_match(
&mut self,
subject: &located_ast::Expr,
cases: &[located_ast::MatchCase],
) -> CompileResult<()> {
eprintln!("match subject: {subject:?}");
eprintln!("match cases: {cases:?}");
Err(self.error(CodegenErrorType::NotImplementedYet))
let mut pattern_context = PatternContext {
current_block: usize::MAX,
blocks: Vec::new(),
allow_irrefutable: false,
};
self.compile_match_inner(subject, cases, &mut pattern_context)?;
Ok(())
}
fn compile_chained_comparison(
@@ -2557,7 +2723,7 @@ impl Compiler {
fn compile_keywords(&mut self, keywords: &[located_ast::Keyword]) -> CompileResult<()> {
let mut size = 0;
let groupby = keywords.iter().group_by(|e| e.arg.is_none());
let groupby = keywords.iter().chunk_by(|e| e.arg.is_none());
for (is_unpacking, sub_keywords) in &groupby {
if is_unpacking {
for keyword in sub_keywords {
@@ -2720,7 +2886,7 @@ impl Compiler {
(false, element)
}
})
.group_by(|(starred, _)| *starred);
.chunk_by(|(starred, _)| *starred);
for (starred, run) in &groups {
let mut run_size = 0;
@@ -3036,16 +3202,17 @@ impl Compiler {
fn switch_to_block(&mut self, block: ir::BlockIdx) {
let code = self.current_code_info();
let prev = code.current_block;
assert_ne!(prev, block, "recursive switching {prev:?} -> {block:?}");
assert_eq!(
code.blocks[block].next,
ir::BlockIdx::NULL,
"switching to completed block"
"switching {prev:?} -> {block:?} to completed block"
);
let prev_block = &mut code.blocks[prev.0 as usize];
assert_eq!(
prev_block.next.0,
u32::MAX,
"switching from block that's already got a next"
"switching {prev:?} -> {block:?} from block that's already got a next"
);
prev_block.next = block;
code.current_block = block;
@@ -3201,17 +3368,51 @@ impl EmitArg<bytecode::Label> for ir::BlockIdx {
}
}
/// Strips leading whitespace from a docstring.
///
/// The code has been ported from `_PyCompile_CleanDoc` in cpython.
/// `inspect.cleandoc` is also a good reference, but has a few incompatibilities.
fn clean_doc(doc: &str) -> String {
let doc = rustpython_common::str::expandtabs(doc, 8);
// First pass: find minimum indentation of any non-blank lines
// after first line.
let margin = doc
.lines()
// Find the non-blank lines
.filter(|line| !line.trim().is_empty())
// get the one with the least indentation
.map(|line| line.chars().take_while(|c| c == &' ').count())
.min();
if let Some(margin) = margin {
let mut cleaned = String::with_capacity(doc.len());
// copy first line without leading whitespace
if let Some(first_line) = doc.lines().next() {
cleaned.push_str(first_line.trim_start());
}
// copy subsequent lines without margin.
for line in doc.split('\n').skip(1) {
cleaned.push('\n');
let cleaned_line = line.chars().skip(margin).collect::<String>();
cleaned.push_str(&cleaned_line);
}
cleaned
} else {
doc.to_owned()
}
}
fn split_doc<'a>(
body: &'a [located_ast::Stmt],
opts: &CompileOpts,
) -> (Option<String>, &'a [located_ast::Stmt]) {
if let Some((located_ast::Stmt::Expr(expr), body_rest)) = body.split_first() {
if let Some(doc) = try_get_constant_string(std::slice::from_ref(&expr.value)) {
if opts.optimize < 2 {
return (Some(doc), body_rest);
return if opts.optimize < 2 {
(Some(clean_doc(&doc)), body_rest)
} else {
return (None, body_rest);
}
(None, body_rest)
};
}
}
(None, body)

View File

@@ -30,6 +30,8 @@ pub enum CodegenErrorType {
TooManyStarUnpack,
EmptyWithItems,
EmptyWithBody,
DuplicateStore(String),
InvalidMatchCase,
NotImplementedYet, // RustPython marker for unimplemented features
}
@@ -75,6 +77,12 @@ impl fmt::Display for CodegenErrorType {
EmptyWithBody => {
write!(f, "empty body on With")
}
DuplicateStore(s) => {
write!(f, "duplicate store {s}")
}
InvalidMatchCase => {
write!(f, "invalid match case")
}
NotImplementedYet => {
write!(f, "RustPython does not implement this feature yet")
}

View File

@@ -886,11 +886,13 @@ impl SymbolTableBuilder {
self.scan_statements(orelse)?;
self.scan_statements(finalbody)?;
}
Stmt::Match(StmtMatch { subject, .. }) => {
return Err(SymbolTableError {
error: "match expression is not implemented yet".to_owned(),
location: Some(subject.location()),
});
Stmt::Match(StmtMatch { subject, cases, .. }) => {
self.scan_expression(subject, ExpressionContext::Load)?;
for case in cases {
// TODO: below
// self.scan_pattern(&case.pattern, ExpressionContext::Load)?;
self.scan_statements(&case.body)?;
}
}
Stmt::Raise(StmtRaise { exc, cause, .. }) => {
if let Some(expression) = exc {
@@ -1255,8 +1257,18 @@ impl SymbolTableBuilder {
self.scan_expression(binding, ExpressionContext::Load)?;
}
}
ast::located::TypeParam::ParamSpec(_) => todo!(),
ast::located::TypeParam::TypeVarTuple(_) => todo!(),
ast::located::TypeParam::ParamSpec(ast::TypeParamParamSpec {
name,
range: param_spec_range,
}) => {
self.register_name(name, SymbolUsage::Assigned, param_spec_range.start)?;
}
ast::located::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple {
name,
range: type_var_tuple_range,
}) => {
self.register_name(name, SymbolUsage::Assigned, type_var_tuple_range.start)?;
}
}
}
Ok(())

View File

@@ -15,7 +15,7 @@ bitflags = { workspace = true }
itertools = { workspace = true }
malachite-bigint = { workspace = true }
num-complex = { workspace = true }
serde = { version = "1.0.133", optional = true, default-features = false, features = ["derive"] }
serde = { version = "1.0.217", optional = true, default-features = false, features = ["derive"] }
lz4_flex = "0.11"

View File

@@ -197,7 +197,7 @@ impl OpArgState {
}
#[inline(always)]
pub fn extend(&mut self, arg: OpArgByte) -> OpArg {
self.state = self.state << 8 | u32::from(arg.0);
self.state = (self.state << 8) | u32::from(arg.0);
OpArg(self.state)
}
#[inline(always)]
@@ -595,10 +595,12 @@ pub enum Instruction {
TypeVarWithBound,
TypeVarWithConstraint,
TypeAlias,
TypeVarTuple,
ParamSpec,
// If you add a new instruction here, be sure to keep LAST_INSTRUCTION updated
}
// This must be kept up to date to avoid marshaling errors
const LAST_INSTRUCTION: Instruction = Instruction::TypeAlias;
const LAST_INSTRUCTION: Instruction = Instruction::ParamSpec;
const _: () = assert!(mem::size_of::<Instruction>() == 1);
impl From<Instruction> for u8 {
@@ -1291,6 +1293,8 @@ impl Instruction {
TypeVarWithBound => -1,
TypeVarWithConstraint => -1,
TypeAlias => -2,
ParamSpec => 0,
TypeVarTuple => 0,
}
}
@@ -1460,6 +1464,8 @@ impl Instruction {
TypeVarWithBound => w!(TypeVarWithBound),
TypeVarWithConstraint => w!(TypeVarWithConstraint),
TypeAlias => w!(TypeAlias),
ParamSpec => w!(ParamSpec),
TypeVarTuple => w!(TypeVarTuple),
}
}
}

View File

@@ -18,10 +18,10 @@ once_cell = { workspace = true }
syn = { workspace = true, features = ["full", "extra-traits"] }
maplit = "1.0.2"
proc-macro2 = "1.0.79"
quote = "1.0.18"
proc-macro2 = "1.0.93"
quote = "1.0.38"
syn-ext = { version = "0.4.0", features = ["full"] }
textwrap = { version = "0.15.0", default-features = false }
textwrap = { version = "0.16.1", default-features = false }
[lints]
workspace = true

View File

@@ -1229,6 +1229,13 @@ impl MethodItemMeta {
let inner = self.inner();
let name = inner._optional_str("name")?;
let magic = inner._bool("magic")?;
if magic && name.is_some() {
bail_span!(
&inner.meta_ident,
"A #[{}] method cannot be magic and have a specified name, choose one.",
inner.meta_name()
);
}
Ok(if let Some(name) = name {
name
} else {

View File

@@ -12,6 +12,124 @@ pub fn derive_from_args(input: TokenStream) -> TokenStream {
derive_impl::derive_from_args(input).into()
}
/// The attribute can be applied either to a struct, trait, or impl.
/// # Struct
/// This implements `MaybeTraverse`, `PyClassDef`, and `StaticType` for the struct.
/// Consider deriving `Traverse` to implement it.
/// ## Arguments
/// - `module`: the module which contains the class -- can be omitted if in a `#[pymodule]`.
/// - `name`: the name of the Python class, by default it is the name of the struct.
/// - `base`: the base class of the Python class.
/// This does not cause inheritance of functions or attributes that must be done by a separate trait.
/// # Impl
/// This part implements `PyClassImpl` for the struct.
/// This includes methods, getters/setters, etc.; only annotated methods will be included.
/// Common functions and abilities like instantiation and `__call__` are often implemented by
/// traits rather than in the `impl` itself; see `Constructor` and `Callable` respectively for those.
/// ## Arguments
/// - `name`: the name of the Python class, when no name is provided the struct name is used.
/// - `flags`: the flags of the class, see `PyTypeFlags`.
/// - `BASETYPE`: allows the class to be inheritable.
/// - `IMMUTABLETYPE`: class attributes are immutable.
/// - `with`: which trait implementations are to be included in the python class.
/// ```rust, ignore
/// #[pyclass(module = "mymodule", name = "MyClass", base = "BaseClass")]
/// struct MyStruct {
/// x: i32,
/// }
///
/// impl Constructor for MyStruct {
/// ...
/// }
///
/// #[pyclass(with(Constructor))]
/// impl MyStruct {
/// ...
/// }
/// ```
/// ## Inner markers
/// ### pymethod/pyclassmethod/pystaticmethod
/// `pymethod` is used to mark a method of the Python class.
/// `pyclassmethod` is used to mark a class method.
/// `pystaticmethod` is used to mark a static method.
/// #### Method signature
/// The first parameter can be either `&self` or `<var>: PyRef<Self>` for `pymethod`.
/// The first parameter can be `cls: PyTypeRef` for `pyclassmethod`.
/// There is no mandatory parameter for `pystaticmethod`.
/// Both are valid and essentially the same, but the latter can yield more control.
/// The last parameter can optionally be of the type `&VirtualMachine` to access the VM.
/// All other values must implement `IntoPyResult`.
/// Numeric types, `String`, `bool`, and `PyObjectRef` implement this trait,
/// but so does any object that implements `PyValue`.
/// Consider using `OptionalArg` for optional arguments.
/// #### Arguments
/// - `magic`: marks the method as a magic method: the method name is surrounded with double underscores.
/// ```rust, ignore
/// #[pyclass]
/// impl MyStruct {
/// // This will be called as the `__add__` method in Python.
/// #[pymethod(magic)]
/// fn add(&self, other: &Self) -> PyResult<i32> {
/// ...
/// }
/// }
/// ```
/// - `name`: the name of the method in Python,
/// by default it is the same as the Rust method, or surrounded by double underscores if magic is present.
/// This overrides `magic` and the default name and cannot be used with `magic` to prevent ambiguity.
/// ### pygetset
/// This is used to mark a getter/setter pair.
/// #### Arguments
/// - `setter`: marks the method as a setter, it acts as a getter by default.
/// Setter method names should be prefixed with `set_`.
/// - `name`: the name of the attribute in Python, by default it is the same as the Rust method.
/// - `magic`: marks the method as a magic method: the method name is surrounded with double underscores.
/// This cannot be used with `name` to prevent ambiguity.
///
/// Ensure both the getter and setter are marked with `name` and `magic` in the same manner.
/// #### Examples
/// ```rust, ignore
/// #[pyclass]
/// impl MyStruct {
/// #[pygetset]
/// fn x(&self) -> PyResult<i32> {
/// Ok(self.x.lock())
/// }
/// #[pygetset(setter)]
/// fn set_x(&mut self, value: i32) -> PyResult<()> {
/// self.x.set(value);
/// Ok(())
/// }
/// }
/// ```
/// ### pyslot
/// This is used to mark a slot method it should be marked by prefixing the method in rust with `slot_`.
/// #### Arguments
/// - name: the name of the slot method.
/// ### pyattr
/// ### extend_class
/// This helps inherit attributes from a parent class.
/// The method this is applied on should be called `extend_class_with_fields`.
/// #### Examples
/// ```rust, ignore
/// #[extend_class]
/// fn extend_class_with_fields(ctx: &Context, class: &'static Py<PyType>) {
/// class.set_attr(
/// identifier!(ctx, _fields),
/// ctx.new_tuple(vec![
/// ctx.new_str(ascii!("body")).into(),
/// ctx.new_str(ascii!("type_ignores")).into(),
/// ])
/// .into(),
/// );
/// class.set_attr(identifier!(ctx, _attributes), ctx.new_list(vec![]).into());
/// }
/// ```
/// ### pymember
/// # Trait
/// `#[pyclass]` on traits functions a lot like `#[pyclass]` on `impl` blocks.
/// Note that associated functions that are annotated with `#[pymethod]` or similar **must**
/// have a body, abstract functions should be wrapped before applying an annotation.
#[proc_macro_attribute]
pub fn pyclass(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr = parse_macro_input!(attr);
@@ -34,6 +152,71 @@ pub fn pyexception(attr: TokenStream, item: TokenStream) -> TokenStream {
derive_impl::pyexception(attr, item).into()
}
/// This attribute must be applied to an inline module.
/// It defines a Python module in the form a `make_module` function in the module;
/// this has to be used in a `get_module_inits` to properly register the module.
/// Additionally, this macro defines 'MODULE_NAME' and 'DOC' in the module.
/// # Arguments
/// - `name`: the name of the python module,
/// by default, it is the name of the module, but this can be configured.
/// ```rust, ignore
/// // This will create a module named `mymodule`
/// #[pymodule(name = "mymodule")]
/// mod module {
/// }
/// ```
/// - `sub`: declare the module as a submodule of another module.
/// ```rust, ignore
/// #[pymodule(sub)]
/// mod submodule {
/// }
///
/// #[pymodule(with(submodule))]
/// mod mymodule {
/// }
/// ```
/// - `with`: declare the list of submodules that this module contains (see `sub` for example).
/// ## Inner markers
/// ### pyattr
/// `pyattr` is a multipurpose marker that can be used in a pymodule.
/// The most common use is to mark a function or class as a part of the module.
/// This can be done by applying it to a function or struct prior to the `#[pyfunction]` or `#[pyclass]` macro.
/// If applied to a constant, it will be added to the module as an attribute.
/// If applied to a function not marked with `pyfunction`,
/// it will also be added to the module as an attribute but the value is the result of the function.
/// If `#[pyattr(once)]` is used in this case, the function will be called once
/// and the result will be stored using a `static_cell`.
/// #### Examples
/// ```rust, ignore
/// #[pymodule]
/// mod mymodule {
/// #[pyattr]
/// const MY_CONSTANT: i32 = 42;
/// #[pyattr]
/// fn another_constant() -> PyResult<i32> {
/// Ok(42)
/// }
/// #[pyattr(once)]
/// fn once() -> PyResult<i32> {
/// // This will only be called once and the result will be stored.
/// Ok(2 ** 24)
/// }
///
/// #[pyattr]
/// #[pyfunction]
/// fn my_function(vm: &VirtualMachine) -> PyResult<()> {
/// ...
/// }
/// }
/// ```
/// ### pyfunction
/// This is used to create a python function.
/// #### Function signature
/// The last argument can optionally be of the type `&VirtualMachine` to access the VM.
/// Refer to the `pymethod` documentation (located in the `pyclass` macro documentation)
/// for more information on what regular argument types are permitted.
/// #### Arguments
/// - `name`: the name of the function in Python, by default it is the same as the associated Rust function.
#[proc_macro_attribute]
pub fn pymodule(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr = parse_macro_input!(attr);

View File

@@ -7,9 +7,7 @@ myobj = MyObject()
assert myobj == myobj
assert not myobj != myobj
object.__subclasshook__() == NotImplemented
object.__subclasshook__(1) == NotImplemented
object.__subclasshook__(1, 2) == NotImplemented
assert MyObject().__eq__(MyObject()) == NotImplemented
assert MyObject().__ne__(MyObject()) == NotImplemented

View File

@@ -0,0 +1,133 @@
import os as _os, sys as _sys
from _ctypes import sizeof
from _ctypes import _SimpleCData
from struct import calcsize as _calcsize
def create_string_buffer(init, size=None):
"""create_string_buffer(aBytes) -> character array
create_string_buffer(anInteger) -> character array
create_string_buffer(aBytes, anInteger) -> character array
"""
if isinstance(init, bytes):
if size is None:
size = len(init)+1
_sys.audit("ctypes.create_string_buffer", init, size)
buftype = c_char * size
buf = buftype()
buf.value = init
return buf
elif isinstance(init, int):
_sys.audit("ctypes.create_string_buffer", None, init)
buftype = c_char * init
buf = buftype()
return buf
raise TypeError(init)
def _check_size(typ, typecode=None):
# Check if sizeof(ctypes_type) against struct.calcsize. This
# should protect somewhat against a misconfigured libffi.
from struct import calcsize
if typecode is None:
# Most _type_ codes are the same as used in struct
typecode = typ._type_
actual, required = sizeof(typ), calcsize(typecode)
if actual != required:
raise SystemError("sizeof(%s) wrong: %d instead of %d" % \
(typ, actual, required))
class c_short(_SimpleCData):
_type_ = "h"
_check_size(c_short)
class c_ushort(_SimpleCData):
_type_ = "H"
_check_size(c_ushort)
class c_long(_SimpleCData):
_type_ = "l"
_check_size(c_long)
class c_ulong(_SimpleCData):
_type_ = "L"
_check_size(c_ulong)
if _calcsize("i") == _calcsize("l"):
# if int and long have the same size, make c_int an alias for c_long
c_int = c_long
c_uint = c_ulong
else:
class c_int(_SimpleCData):
_type_ = "i"
_check_size(c_int)
class c_uint(_SimpleCData):
_type_ = "I"
_check_size(c_uint)
class c_float(_SimpleCData):
_type_ = "f"
_check_size(c_float)
class c_double(_SimpleCData):
_type_ = "d"
_check_size(c_double)
class c_longdouble(_SimpleCData):
_type_ = "g"
if sizeof(c_longdouble) == sizeof(c_double):
c_longdouble = c_double
if _calcsize("l") == _calcsize("q"):
# if long and long long have the same size, make c_longlong an alias for c_long
c_longlong = c_long
c_ulonglong = c_ulong
else:
class c_longlong(_SimpleCData):
_type_ = "q"
_check_size(c_longlong)
class c_ulonglong(_SimpleCData):
_type_ = "Q"
## def from_param(cls, val):
## return ('d', float(val), val)
## from_param = classmethod(from_param)
_check_size(c_ulonglong)
class c_ubyte(_SimpleCData):
_type_ = "B"
c_ubyte.__ctype_le__ = c_ubyte.__ctype_be__ = c_ubyte
# backward compatibility:
##c_uchar = c_ubyte
_check_size(c_ubyte)
class c_byte(_SimpleCData):
_type_ = "b"
c_byte.__ctype_le__ = c_byte.__ctype_be__ = c_byte
_check_size(c_byte)
class c_char(_SimpleCData):
_type_ = "c"
c_char.__ctype_le__ = c_char.__ctype_be__ = c_char
_check_size(c_char)
class c_char_p(_SimpleCData):
_type_ = "z"
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, c_void_p.from_buffer(self).value)
_check_size(c_char_p, "P")
class c_void_p(_SimpleCData):
_type_ = "P"
c_voidp = c_void_p # backwards compatibility (to a bug)
_check_size(c_void_p)
class c_bool(_SimpleCData):
_type_ = "?"
_check_size(c_bool)
i = c_int(42)
f = c_float(3.14)
# s = create_string_buffer(b'\000' * 32)
assert i.value == 42
assert abs(f.value - 3.14) < 1e-06

View File

@@ -1,27 +0,0 @@
# unittest for modified imghdr.py
# Should be replace it into https://github.com/python/cpython/blob/main/Lib/test/test_imghdr.py
import os
import imghdr
TEST_FILES = (
#('python.png', 'png'),
('python.gif', 'gif'),
('python.bmp', 'bmp'),
('python.ppm', 'ppm'),
('python.pgm', 'pgm'),
('python.pbm', 'pbm'),
('python.jpg', 'jpeg'),
('python.ras', 'rast'),
#('python.sgi', 'rgb'),
('python.tiff', 'tiff'),
('python.xbm', 'xbm'),
('python.webp', 'webp'),
('python.exr', 'exr'),
)
resource_dir = os.path.join(os.path.dirname(__file__), 'imghdrdata')
for fname, expected in TEST_FILES:
res = imghdr.what(os.path.join(resource_dir, fname))
assert res == expected

View File

@@ -16,7 +16,7 @@ else:
return ["cmd", "/C", f"echo {text}"]
def sleep(secs):
# TODO: make work in a non-unixy environment (something with timeout.exe?)
return ["sleep", str(secs)]
return ["powershell", "/C", "sleep", str(secs)]
p = subprocess.Popen(echo("test"))
@@ -32,7 +32,7 @@ p = subprocess.Popen(sleep(2))
assert p.poll() is None
with assert_raises(subprocess.TimeoutExpired):
assert p.wait(1)
assert p.wait(1)
p.wait()
@@ -48,17 +48,17 @@ p = subprocess.Popen(sleep(2))
p.terminate()
p.wait()
if is_unix:
assert p.returncode == -signal.SIGTERM
assert p.returncode == -signal.SIGTERM
else:
assert p.returncode == 1
assert p.returncode == 1
p = subprocess.Popen(sleep(2))
p.kill()
p.wait()
if is_unix:
assert p.returncode == -signal.SIGKILL
assert p.returncode == -signal.SIGKILL
else:
assert p.returncode == 1
assert p.returncode == 1
p = subprocess.Popen(echo("test"), stdout=subprocess.PIPE)
(stdout, stderr) = p.communicate()
@@ -66,4 +66,4 @@ assert stdout.strip() == b"test"
p = subprocess.Popen(sleep(5), stdout=subprocess.PIPE)
with assert_raises(subprocess.TimeoutExpired):
p.communicate(timeout=1)
p.communicate(timeout=1)

View File

@@ -1,12 +0,0 @@
# This probably will be superceeded by the python unittests when that works.
import xdrlib
p = xdrlib.Packer()
p.pack_int(1337)
d = p.get_buffer()
print(d)
# assert d == b'\x00\x00\x059'

View File

@@ -50,7 +50,7 @@ class Bar:
assert x == 3
assert Bar.__doc__ == " W00t "
assert Bar.__doc__ == "W00t "
bar = Bar(42)
assert bar.get_x.__doc__ == None
@@ -147,7 +147,7 @@ class T3:
test3
"""
assert T3.__doc__ == "\n test3\n "
assert T3.__doc__ == "\ntest3\n"
class T4:

View File

@@ -0,0 +1,15 @@
def f1():
"""
x
\ty
"""
assert f1.__doc__ == '\nx\ny\n'
def f2():
"""
\t x
\t\ty
"""
assert f2.__doc__ == '\nx\n y\n'

View File

@@ -44,7 +44,7 @@ def f3():
"""
pass
assert f3.__doc__ == "\n test3\n "
assert f3.__doc__ == "\ntest3\n"
def f4():
"test4"

View File

@@ -107,7 +107,7 @@ impl<'vm> ShellHelper<'vm> {
.filter(|res| {
res.as_ref()
.ok()
.map_or(true, |s| s.as_str().starts_with(word_start))
.is_none_or(|s| s.as_str().starts_with(word_start))
})
.collect::<Result<Vec<_>, _>>()
.ok()?;

View File

@@ -46,16 +46,15 @@ thread_local = { workspace = true }
memchr = { workspace = true }
base64 = "0.13.0"
csv-core = "0.1.10"
csv-core = "0.1.11"
dyn-clone = "1.0.10"
libz-sys = { version = "1.1", default-features = false, optional = true }
puruspe = "0.2.4"
puruspe = "0.4.0"
xml-rs = "0.8.14"
# random
rand = { workspace = true }
rand_core = "0.6.3"
mt19937 = "2.0.1"
mt19937 = "3.1"
# Crypto:
digest = "0.10.3"
@@ -88,12 +87,12 @@ bzip2 = { version = "0.4", optional = true }
# uuid
[target.'cfg(not(any(target_os = "ios", target_os = "android", target_os = "windows", target_arch = "wasm32", target_os = "redox")))'.dependencies]
mac_address = "1.1.3"
uuid = { version = "1.1.2", features = ["v1", "fast-rng"] }
uuid = { version = "1.1.2", features = ["v1"] }
# mmap
[target.'cfg(all(unix, not(target_arch = "wasm32")))'.dependencies]
memmap2 = "0.5.4"
page_size = "0.4"
memmap2 = "0.5.10"
page_size = "0.6"
[target.'cfg(all(unix, not(target_os = "redox"), not(target_os = "ios")))'.dependencies]
termios = "0.3.3"
@@ -102,8 +101,8 @@ termios = "0.3.3"
rustix = { workspace = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
gethostname = "0.2.3"
socket2 = { version = "0.5.6", features = ["all"] }
gethostname = "1.0.0"
socket2 = { version = "0.5.8", features = ["all"] }
dns-lookup = "2"
openssl = { version = "0.10.66", optional = true }
openssl-sys = { version = "0.9.80", optional = true }
@@ -136,7 +135,7 @@ features = [
]
[target.'cfg(target_os = "macos")'.dependencies]
system-configuration = "0.5.0"
system-configuration = "0.5.1"
[lints]
workspace = true

View File

@@ -76,7 +76,7 @@ mod decl {
let mut unhex = Vec::<u8>::with_capacity(hex_bytes.len() / 2);
for (n1, n2) in hex_bytes.iter().tuples() {
if let (Some(n1), Some(n2)) = (unhex_nibble(*n1), unhex_nibble(*n2)) {
unhex.push(n1 << 4 | n2);
unhex.push((n1 << 4) | n2);
} else {
return Err(super::new_binascii_error(
"Non-hexadecimal digit found".to_owned(),
@@ -343,7 +343,7 @@ mod decl {
if let (Some(ch1), Some(ch2)) =
(unhex_nibble(buffer[idx]), unhex_nibble(buffer[idx + 1]))
{
out_data.push(ch1 << 4 | ch2);
out_data.push((ch1 << 4) | ch2);
}
idx += 2;
} else {
@@ -661,19 +661,19 @@ mod decl {
};
if res.len() < length {
res.push(char_a << 2 | char_b >> 4);
res.push((char_a << 2) | (char_b >> 4));
} else if char_a != 0 || char_b != 0 {
return trailing_garbage_error();
}
if res.len() < length {
res.push((char_b & 0xf) << 4 | char_c >> 2);
res.push(((char_b & 0xf) << 4) | (char_c >> 2));
} else if char_c != 0 {
return trailing_garbage_error();
}
if res.len() < length {
res.push((char_c & 0x3) << 6 | char_d);
res.push(((char_c & 0x3) << 6) | char_d);
} else if char_d != 0 {
return trailing_garbage_error();
}
@@ -725,8 +725,8 @@ mod decl {
let char_c = *chunk.get(2).unwrap_or(&0);
res.push(uu_b2a(char_a >> 2, backtick));
res.push(uu_b2a((char_a & 0x3) << 4 | char_b >> 4, backtick));
res.push(uu_b2a((char_b & 0xf) << 2 | char_c >> 6, backtick));
res.push(uu_b2a(((char_a & 0x3) << 4) | (char_b >> 4), backtick));
res.push(uu_b2a(((char_b & 0xf) << 2) | (char_c >> 6), backtick));
res.push(uu_b2a(char_c & 0x3f, backtick));
}

View File

@@ -132,6 +132,9 @@ mod math {
#[pyfunction]
fn log(x: PyObjectRef, base: OptionalArg<ArgIntoFloat>, vm: &VirtualMachine) -> PyResult<f64> {
let base = base.map(|b| *b).unwrap_or(std::f64::consts::E);
if base.is_sign_negative() {
return Err(vm.new_value_error("math domain error".to_owned()));
}
log2(x, vm).map(|logx| logx / base.log2())
}
@@ -192,16 +195,18 @@ mod math {
let x = *x;
let y = *y;
if x < 0.0 && x.is_finite() && y.fract() != 0.0 && y.is_finite() {
return Err(vm.new_value_error("math domain error".to_owned()));
}
if x == 0.0 && y < 0.0 && y != f64::NEG_INFINITY {
if x < 0.0 && x.is_finite() && y.fract() != 0.0 && y.is_finite()
|| x == 0.0 && y < 0.0 && y != f64::NEG_INFINITY
{
return Err(vm.new_value_error("math domain error".to_owned()));
}
let value = x.powf(y);
if x.is_finite() && y.is_finite() && value.is_infinite() {
return Err(vm.new_overflow_error("math range error".to_string()));
}
Ok(value)
}
@@ -212,6 +217,9 @@ mod math {
return Ok(value);
}
if value.is_sign_negative() {
if value.is_zero() {
return Ok(-0.0f64);
}
return Err(vm.new_value_error("math domain error".to_owned()));
}
Ok(value.sqrt())
@@ -260,6 +268,9 @@ mod math {
#[pyfunction]
fn cos(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> {
if x.is_infinite() {
return Err(vm.new_value_error("math domain error".to_owned()));
}
call_math_func!(cos, x, vm)
}
@@ -345,7 +356,7 @@ mod math {
.map(|x| (x / scale).powi(2))
.chain(std::iter::once(-norm * norm))
// Pairwise summation of floats gives less rounding error than a naive sum.
.tree_fold1(std::ops::Add::add)
.tree_reduce(std::ops::Add::add)
.expect("expected at least 1 element");
norm = norm + correction / (2.0 * norm);
}
@@ -394,11 +405,17 @@ mod math {
#[pyfunction]
fn sin(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> {
if x.is_infinite() {
return Err(vm.new_value_error("math domain error".to_owned()));
}
call_math_func!(sin, x, vm)
}
#[pyfunction]
fn tan(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> {
if x.is_infinite() {
return Err(vm.new_value_error("math domain error".to_owned()));
}
call_math_func!(tan, x, vm)
}
@@ -612,7 +629,7 @@ mod math {
#[pyfunction]
fn fsum(seq: ArgIterable<ArgIntoFloat>, vm: &VirtualMachine) -> PyResult<f64> {
let mut partials = vec![];
let mut partials = Vec::with_capacity(32);
let mut special_sum = 0.0;
let mut inf_sum = 0.0;
@@ -620,11 +637,11 @@ mod math {
let mut x = *obj?;
let xsave = x;
let mut j = 0;
let mut i = 0;
// This inner loop applies `hi`/`lo` summation to each
// partial so that the list of partial sums remains exact.
for i in 0..partials.len() {
let mut y: f64 = partials[i];
for j in 0..partials.len() {
let mut y: f64 = partials[j];
if x.abs() < y.abs() {
std::mem::swap(&mut x, &mut y);
}
@@ -633,33 +650,33 @@ mod math {
let hi = x + y;
let lo = y - (hi - x);
if lo != 0.0 {
partials[j] = lo;
j += 1;
partials[i] = lo;
i += 1;
}
x = hi;
}
if !x.is_finite() {
// a nonfinite x could arise either as
// a result of intermediate overflow, or
// as a result of a nan or inf in the
// summands
if xsave.is_finite() {
return Err(vm.new_overflow_error("intermediate overflow in fsum".to_owned()));
partials.truncate(i);
if x != 0.0 {
if !x.is_finite() {
// a nonfinite x could arise either as
// a result of intermediate overflow, or
// as a result of a nan or inf in the
// summands
if xsave.is_finite() {
return Err(
vm.new_overflow_error("intermediate overflow in fsum".to_owned())
);
}
if xsave.is_infinite() {
inf_sum += xsave;
}
special_sum += xsave;
// reset partials
partials.clear();
} else {
partials.push(x);
}
if xsave.is_infinite() {
inf_sum += xsave;
}
special_sum += xsave;
// reset partials
partials.clear();
}
if j >= partials.len() {
partials.push(x);
} else {
partials[j] = x;
partials.truncate(j + 1);
}
}
if special_sum != 0.0 {
@@ -814,9 +831,38 @@ mod math {
(x.fract(), x.trunc())
}
#[derive(FromArgs)]
struct NextAfterArgs {
#[pyarg(positional)]
x: ArgIntoFloat,
#[pyarg(positional)]
y: ArgIntoFloat,
#[pyarg(named, optional)]
steps: OptionalArg<ArgIndex>,
}
#[pyfunction]
fn nextafter(x: ArgIntoFloat, y: ArgIntoFloat) -> f64 {
float_ops::nextafter(*x, *y)
fn nextafter(arg: NextAfterArgs, vm: &VirtualMachine) -> PyResult<f64> {
let steps: Option<i64> = arg
.steps
.map(|v| v.try_to_primitive(vm))
.transpose()?
.into_option();
match steps {
Some(steps) => {
if steps < 0 {
return Err(
vm.new_value_error("steps must be a non-negative integer".to_string())
);
}
Ok(float_ops::nextafter_with_steps(
*arg.x,
*arg.y,
steps as u64,
))
}
None => Ok(float_ops::nextafter(*arg.x, *arg.y)),
}
}
#[pyfunction]
@@ -900,10 +946,38 @@ mod math {
// refer: https://github.com/python/cpython/blob/main/Modules/mathmodule.c#L3093-L3193
for obj in iter.iter(vm)? {
let obj = obj?;
result = vm._mul(&result, &obj)?;
}
result = vm
._mul(&result, &obj)
.map_err(|_| vm.new_type_error("math type error".to_owned()))?;
Ok(result)
}
#[pyfunction]
fn sumprod(
p: ArgIterable<PyObjectRef>,
q: ArgIterable<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyObjectRef> {
let mut p_iter = p.iter(vm)?;
let mut q_iter = q.iter(vm)?;
// We cannot just create a float because the iterator may contain
// anything as long as it supports __add__ and __mul__.
let mut result = vm.new_pyobj(0);
loop {
let m_p = p_iter.next();
let m_q = q_iter.next();
match (m_p, m_q) {
(Some(r_p), Some(r_q)) => {
let p = r_p?;
let q = r_q?;
let tmp = vm._mul(&p, &q)?;
result = vm._add(&result, &tmp)?;
}
(None, None) => break,
_ => {
return Err(vm.new_value_error("Inputs are not the same length".to_string()));
}
}
}
Ok(result)

View File

@@ -23,7 +23,7 @@ mod _random {
impl Default for PyRng {
fn default() -> Self {
PyRng::Std(Box::new(StdRng::from_entropy()))
PyRng::Std(Box::new(StdRng::from_os_rng()))
}
}
@@ -46,12 +46,6 @@ mod _random {
Self::MT(m) => m.fill_bytes(dest),
}
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
match self {
Self::Std(s) => s.try_fill_bytes(dest),
Self::MT(m) => m.try_fill_bytes(dest),
}
}
}
#[pyattr]

View File

@@ -612,7 +612,11 @@ mod _ssl {
Ok(pbuf.to_vec())
})?;
ctx.set_alpn_select_callback(move |_, client| {
ssl::select_next_proto(&server, client).ok_or(ssl::AlpnError::NOACK)
let proto =
ssl::select_next_proto(&server, client).ok_or(ssl::AlpnError::NOACK)?;
let pos = memchr::memmem::find(client, proto)
.expect("selected alpn proto should be present in client protos");
Ok(&client[pos..proto.len()])
});
Ok(())
}

View File

@@ -5,33 +5,19 @@ mod _uuid {
use crate::{builtins::PyNone, vm::VirtualMachine};
use mac_address::get_mac_address;
use once_cell::sync::OnceCell;
use rand::Rng;
use std::time::{Duration, SystemTime};
use uuid::{
v1::{Context, Timestamp},
Uuid,
};
use uuid::{timestamp::Timestamp, Context, Uuid};
fn get_node_id() -> [u8; 6] {
match get_mac_address() {
Ok(Some(_ma)) => get_mac_address().unwrap().unwrap().bytes(),
_ => rand::thread_rng().gen::<[u8; 6]>(),
_ => rand::random::<[u8; 6]>(),
}
}
pub fn now_unix_duration() -> Duration {
use std::time::UNIX_EPOCH;
let now = SystemTime::now();
now.duration_since(UNIX_EPOCH)
.expect("SystemTime before UNIX EPOCH!")
}
#[pyfunction]
fn generate_time_safe() -> (Vec<u8>, PyNone) {
static CONTEXT: Context = Context::new(0);
let now = now_unix_duration();
let ts = Timestamp::from_unix(&CONTEXT, now.as_secs(), now.subsec_nanos());
let ts = Timestamp::now(&CONTEXT);
static NODE_ID: OnceCell<[u8; 6]> = OnceCell::new();
let unique_node_id = NODE_ID.get_or_init(get_node_id);

View File

@@ -23,7 +23,7 @@ ast = ["rustpython-ast"]
codegen = ["rustpython-codegen", "ast"]
parser = ["rustpython-parser", "ast"]
serde = ["dep:serde"]
wasmbind = ["chrono/wasmbind", "getrandom/js", "wasm-bindgen"]
wasmbind = ["chrono/wasmbind", "getrandom/wasm_js", "wasm-bindgen"]
[dependencies]
rustpython-compiler = { workspace = true, optional = true }
@@ -99,6 +99,9 @@ uname = "0.1.1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rustyline = { workspace = true }
which = "6"
errno = "0.3"
libloading = "0.8"
widestring = { workspace = true }
[target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies]
num_cpus = "1.13.1"
@@ -106,8 +109,7 @@ num_cpus = "1.13.1"
[target.'cfg(windows)'.dependencies]
junction = { workspace = true }
schannel = { workspace = true }
widestring = { workspace = true }
winreg = "0.52"
winreg = "0.55"
[target.'cfg(windows)'.dependencies.windows]
version = "0.52.0"
@@ -115,6 +117,7 @@ features = [
"Win32_Foundation",
"Win32_System_LibraryLoader",
"Win32_System_Threading",
"Win32_System_Time",
"Win32_UI_Shell",
]
@@ -122,6 +125,7 @@ features = [
workspace = true
features = [
"Win32_Foundation",
"Win32_Globalization",
"Win32_Networking_WinSock",
"Win32_Security",
"Win32_Storage_FileSystem",
@@ -143,7 +147,7 @@ features = [
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
wasm-bindgen = { workspace = true, optional = true }
getrandom = { workspace = true, features = ["custom"] }
getrandom = { workspace = true }
[build-dependencies]
glob = { workspace = true }

View File

@@ -124,8 +124,8 @@ fn inner_divmod(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult<(f64, f64)> {
pub fn float_pow(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult {
if v1.is_zero() && v2.is_sign_negative() {
let msg = format!("{v1} cannot be raised to a negative power");
Err(vm.new_zero_division_error(msg))
let msg = "0.0 cannot be raised to a negative power";
Err(vm.new_zero_division_error(msg.to_owned()))
} else if v1.is_sign_negative() && (v2.floor() - v2).abs() > f64::EPSILON {
let v1 = Complex64::new(v1, 0.);
let v2 = Complex64::new(v2, 0.);

View File

@@ -113,7 +113,7 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine)
// ));
// }
let state = if obj.dict().map_or(true, |d| d.is_empty()) {
let state = if obj.dict().is_none_or(|d| d.is_empty()) {
vm.ctx.none()
} else {
// let state = object_get_dict(obj.clone(), obj.ctx()).unwrap();

View File

@@ -1125,33 +1125,7 @@ impl PyStr {
#[pymethod]
fn expandtabs(&self, args: anystr::ExpandTabsArgs) -> String {
let tab_stop = args.tabsize();
let mut expanded_str = String::with_capacity(self.byte_len());
let mut tab_size = tab_stop;
let mut col_count = 0usize;
for ch in self.as_str().chars() {
match ch {
'\t' => {
let num_spaces = tab_size - col_count;
col_count += num_spaces;
let expand = " ".repeat(num_spaces);
expanded_str.push_str(&expand);
}
'\r' | '\n' => {
expanded_str.push(ch);
col_count = 0;
tab_size = 0;
}
_ => {
expanded_str.push(ch);
col_count += 1;
}
}
if col_count >= tab_size {
tab_size += tab_stop;
}
}
expanded_str
rustpython_common::str::expandtabs(self.as_str(), args.tabsize())
}
#[pymethod]
@@ -1591,80 +1565,6 @@ impl AsRef<str> for PyExact<PyStr> {
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Interpreter;
#[test]
fn str_title() {
let tests = vec![
(" Hello ", " hello "),
("Hello ", "hello "),
("Hello ", "Hello "),
("Format This As Title String", "fOrMaT thIs aS titLe String"),
("Format,This-As*Title;String", "fOrMaT,thIs-aS*titLe;String"),
("Getint", "getInt"),
("Greek Ωppercases ...", "greek ωppercases ..."),
("Greek ῼitlecases ...", "greek ῳitlecases ..."),
];
for (title, input) in tests {
assert_eq!(PyStr::from(input).title().as_str(), title);
}
}
#[test]
fn str_istitle() {
let pos = vec![
"A",
"A Titlecased Line",
"A\nTitlecased Line",
"A Titlecased, Line",
"Greek Ωppercases ...",
"Greek ῼitlecases ...",
];
for s in pos {
assert!(PyStr::from(s).istitle());
}
let neg = vec![
"",
"a",
"\n",
"Not a capitalized String",
"Not\ta Titlecase String",
"Not--a Titlecase String",
"NOT",
];
for s in neg {
assert!(!PyStr::from(s).istitle());
}
}
#[test]
fn str_maketrans_and_translate() {
Interpreter::without_stdlib(Default::default()).enter(|vm| {
let table = vm.ctx.new_dict();
table
.set_item("a", vm.ctx.new_str("🎅").into(), vm)
.unwrap();
table.set_item("b", vm.ctx.none(), vm).unwrap();
table
.set_item("c", vm.ctx.new_str(ascii!("xda")).into(), vm)
.unwrap();
let translated =
PyStr::maketrans(table.into(), OptionalArg::Missing, OptionalArg::Missing, vm)
.unwrap();
let text = PyStr::from("abc");
let translated = text.translate(translated, vm).unwrap();
assert_eq!(translated, "🎅xda".to_owned());
let translated = text.translate(vm.ctx.new_int(3).into(), vm);
assert_eq!("TypeError", &*translated.unwrap_err().class().name(),);
})
}
}
impl AnyStrWrapper for PyStrRef {
type Str = str;
fn as_ref(&self) -> &str {
@@ -1806,3 +1706,77 @@ impl AsRef<str> for PyStrInterned {
self.as_str()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Interpreter;
#[test]
fn str_title() {
let tests = vec![
(" Hello ", " hello "),
("Hello ", "hello "),
("Hello ", "Hello "),
("Format This As Title String", "fOrMaT thIs aS titLe String"),
("Format,This-As*Title;String", "fOrMaT,thIs-aS*titLe;String"),
("Getint", "getInt"),
("Greek Ωppercases ...", "greek ωppercases ..."),
("Greek ῼitlecases ...", "greek ῳitlecases ..."),
];
for (title, input) in tests {
assert_eq!(PyStr::from(input).title().as_str(), title);
}
}
#[test]
fn str_istitle() {
let pos = vec![
"A",
"A Titlecased Line",
"A\nTitlecased Line",
"A Titlecased, Line",
"Greek Ωppercases ...",
"Greek ῼitlecases ...",
];
for s in pos {
assert!(PyStr::from(s).istitle());
}
let neg = vec![
"",
"a",
"\n",
"Not a capitalized String",
"Not\ta Titlecase String",
"Not--a Titlecase String",
"NOT",
];
for s in neg {
assert!(!PyStr::from(s).istitle());
}
}
#[test]
fn str_maketrans_and_translate() {
Interpreter::without_stdlib(Default::default()).enter(|vm| {
let table = vm.ctx.new_dict();
table
.set_item("a", vm.ctx.new_str("🎅").into(), vm)
.unwrap();
table.set_item("b", vm.ctx.none(), vm).unwrap();
table
.set_item("c", vm.ctx.new_str(ascii!("xda")).into(), vm)
.unwrap();
let translated =
PyStr::maketrans(table.into(), OptionalArg::Missing, OptionalArg::Missing, vm)
.unwrap();
let text = PyStr::from("abc");
let translated = text.translate(translated, vm).unwrap();
assert_eq!(translated, "🎅xda".to_owned());
let translated = text.translate(vm.ctx.new_int(3).into(), vm);
assert_eq!("TypeError", &*translated.unwrap_err().class().name(),);
})
}
}

View File

@@ -619,11 +619,16 @@ fn surrogatepass_errors(err: PyObjectRef, vm: &VirtualMachine) -> PyResult<(PyOb
// Not supported, fail with original exception
return Err(err.downcast().unwrap());
}
debug_assert!(range.start <= 0.max(s.len() - 1));
debug_assert!(range.end >= 1.min(s.len()));
debug_assert!(range.end <= s.len());
let mut c: u32 = 0;
// Try decoding a single surrogate character. If there are more,
// let the codec call us again.
let p = &s.as_bytes()[range.start..];
if p.len() - range.start >= byte_length {
if p.len().overflowing_sub(range.start).0 >= byte_length {
match standard_encoding {
StandardEncoding::Utf8 => {
if (p[0] as u32 & 0xf0) == 0xe0
@@ -637,10 +642,10 @@ fn surrogatepass_errors(err: PyObjectRef, vm: &VirtualMachine) -> PyResult<(PyOb
}
}
StandardEncoding::Utf16Le => {
c = (p[1] as u32) << 8 | p[0] as u32;
c = ((p[1] as u32) << 8) | p[0] as u32;
}
StandardEncoding::Utf16Be => {
c = (p[0] as u32) << 8 | p[1] as u32;
c = ((p[0] as u32) << 8) | p[1] as u32;
}
StandardEncoding::Utf32Le => {
c = ((p[3] as u32) << 24)

View File

@@ -374,7 +374,9 @@ fn write_traceback_entry<W: Write>(
writeln!(
output,
r##" File "{}", line {}, in {}"##,
filename, tb_entry.lineno, tb_entry.frame.code.obj_name
filename.trim_start_matches(r"\\?\"),
tb_entry.lineno,
tb_entry.frame.code.obj_name
)?;
print_source_line(output, filename, tb_entry.lineno.to_usize())?;

View File

@@ -1202,6 +1202,23 @@ impl ExecutingFrame<'_> {
self.push_value(type_alias.into_ref(&vm.ctx).into());
Ok(None)
}
bytecode::Instruction::ParamSpec => {
let param_spec_name = self.pop_value();
let param_spec: PyObjectRef = _typing::make_paramspec(param_spec_name.clone())
.into_ref(&vm.ctx)
.into();
self.push_value(param_spec);
Ok(None)
}
bytecode::Instruction::TypeVarTuple => {
let type_var_tuple_name = self.pop_value();
let type_var_tuple: PyObjectRef =
_typing::make_typevartuple(type_var_tuple_name.clone())
.into_ref(&vm.ctx)
.into();
self.push_value(type_var_tuple);
Ok(None)
}
}
}

View File

@@ -8,7 +8,6 @@ use crate::{
vm::{thread, VirtualMachine},
AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
};
use rand::Rng;
pub(crate) fn init_importlib_base(vm: &mut VirtualMachine) -> PyResult<PyObjectRef> {
flame_guard!("init importlib");
@@ -50,7 +49,7 @@ pub(crate) fn init_importlib_package(vm: &VirtualMachine, importlib: PyObjectRef
let mut magic = get_git_revision().into_bytes();
magic.truncate(4);
if magic.len() != 4 {
magic = rand::thread_rng().gen::<[u8; 4]>().to_vec();
magic = rand::random::<[u8; 4]>().to_vec();
}
let magic: PyObjectRef = vm.ctx.new_bytes(magic).into();
importlib_external.set_attr("MAGIC_NUMBER", magic, vm)?;

226
vm/src/stdlib/ctypes.rs Normal file
View File

@@ -0,0 +1,226 @@
pub(crate) mod array;
pub(crate) mod base;
pub(crate) mod function;
pub(crate) mod library;
pub(crate) mod pointer;
pub(crate) mod structure;
pub(crate) mod union;
use crate::builtins::PyModule;
use crate::class::PyClassImpl;
use crate::stdlib::ctypes::base::{PyCData, PyCSimple, PySimpleMeta};
use crate::{Py, PyRef, VirtualMachine};
pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py<PyModule>) {
let ctx = &vm.ctx;
PySimpleMeta::make_class(ctx);
extend_module!(vm, module, {
"_CData" => PyCData::make_class(ctx),
"_SimpleCData" => PyCSimple::make_class(ctx),
"Array" => array::PyCArray::make_class(ctx),
"CFuncPtr" => function::PyCFuncPtr::make_class(ctx),
"_Pointer" => pointer::PyCPointer::make_class(ctx),
"_pointer_type_cache" => ctx.new_dict(),
"Structure" => structure::PyCStructure::make_class(ctx),
"Union" => union::PyCUnion::make_class(ctx),
})
}
pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
let module = _ctypes::make_module(vm);
extend_module_nodes(vm, &module);
module
}
#[pymodule]
pub(crate) mod _ctypes {
use super::base::PyCSimple;
use crate::builtins::PyTypeRef;
use crate::class::StaticType;
use crate::function::Either;
use crate::stdlib::ctypes::library;
use crate::{AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine};
use crossbeam_utils::atomic::AtomicCell;
use std::ffi::{
c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong,
c_ulonglong,
};
use std::mem;
use widestring::WideChar;
#[pyattr(name = "__version__")]
const __VERSION__: &str = "1.1.0";
// TODO: get properly
#[pyattr(name = "RTLD_LOCAL")]
const RTLD_LOCAL: i32 = 0;
// TODO: get properly
#[pyattr(name = "RTLD_GLOBAL")]
const RTLD_GLOBAL: i32 = 0;
#[cfg(target_os = "windows")]
#[pyattr(name = "SIZEOF_TIME_T")]
pub const SIZEOF_TIME_T: usize = 8;
#[cfg(not(target_os = "windows"))]
#[pyattr(name = "SIZEOF_TIME_T")]
pub const SIZEOF_TIME_T: usize = 4;
#[pyattr(name = "CTYPES_MAX_ARGCOUNT")]
pub const CTYPES_MAX_ARGCOUNT: usize = 1024;
#[pyattr]
pub const FUNCFLAG_STDCALL: u32 = 0x0;
#[pyattr]
pub const FUNCFLAG_CDECL: u32 = 0x1;
#[pyattr]
pub const FUNCFLAG_HRESULT: u32 = 0x2;
#[pyattr]
pub const FUNCFLAG_PYTHONAPI: u32 = 0x4;
#[pyattr]
pub const FUNCFLAG_USE_ERRNO: u32 = 0x8;
#[pyattr]
pub const FUNCFLAG_USE_LASTERROR: u32 = 0x10;
#[pyattr]
pub const TYPEFLAG_ISPOINTER: u32 = 0x100;
#[pyattr]
pub const TYPEFLAG_HASPOINTER: u32 = 0x200;
#[pyattr]
pub const DICTFLAG_FINAL: u32 = 0x1000;
#[pyattr(name = "ArgumentError", once)]
fn argument_error(vm: &VirtualMachine) -> PyTypeRef {
vm.ctx.new_exception_type(
"_ctypes",
"ArgumentError",
Some(vec![vm.ctx.exceptions.exception_type.to_owned()]),
)
}
#[pyattr(name = "FormatError", once)]
fn format_error(vm: &VirtualMachine) -> PyTypeRef {
vm.ctx.new_exception_type(
"_ctypes",
"FormatError",
Some(vec![vm.ctx.exceptions.exception_type.to_owned()]),
)
}
pub fn get_size(ty: &str) -> usize {
match ty {
"u" => mem::size_of::<WideChar>(),
"c" | "b" => mem::size_of::<c_schar>(),
"h" => mem::size_of::<c_short>(),
"H" => mem::size_of::<c_short>(),
"i" => mem::size_of::<c_int>(),
"I" => mem::size_of::<c_uint>(),
"l" => mem::size_of::<c_long>(),
"q" => mem::size_of::<c_longlong>(),
"L" => mem::size_of::<c_ulong>(),
"Q" => mem::size_of::<c_ulonglong>(),
"f" => mem::size_of::<c_float>(),
"d" | "g" => mem::size_of::<c_double>(),
"?" | "B" => mem::size_of::<c_uchar>(),
"P" | "z" | "Z" => mem::size_of::<usize>(),
_ => unreachable!(),
}
}
const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?";
pub fn new_simple_type(
cls: Either<&PyObjectRef, &PyTypeRef>,
vm: &VirtualMachine,
) -> PyResult<PyCSimple> {
let cls = match cls {
Either::A(obj) => obj,
Either::B(typ) => typ.as_object(),
};
if let Ok(_type_) = cls.get_attr("_type_", vm) {
if _type_.is_instance((&vm.ctx.types.str_type).as_ref(), vm)? {
let tp_str = _type_.str(vm)?.to_string();
if tp_str.len() != 1 {
Err(vm.new_value_error(
format!("class must define a '_type_' attribute which must be a string of length 1, str: {tp_str}"),
))
} else if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) {
Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\n a single character string containing one of {SIMPLE_TYPE_CHARS}, currently it is {tp_str}.")))
} else {
Ok(PyCSimple {
_type_: tp_str,
value: AtomicCell::new(vm.ctx.none()),
})
}
} else {
Err(vm.new_type_error("class must define a '_type_' string attribute".to_string()))
}
} else {
Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string()))
}
}
#[pyfunction(name = "sizeof")]
pub fn size_of(tp: Either<PyTypeRef, PyObjectRef>, vm: &VirtualMachine) -> PyResult<usize> {
match tp {
Either::A(type_) if type_.fast_issubclass(PyCSimple::static_type()) => {
let zelf = new_simple_type(Either::B(&type_), vm)?;
Ok(get_size(zelf._type_.as_str()))
}
Either::B(obj) if obj.has_attr("size_of_instances", vm)? => {
let size_of_method = obj.get_attr("size_of_instances", vm)?;
let size_of_return = size_of_method.call(vec![], vm)?;
Ok(usize::try_from_object(vm, size_of_return)?)
}
_ => Err(vm.new_type_error("this type has no size".to_string())),
}
}
#[pyfunction(name = "LoadLibrary")]
fn load_library(name: String, vm: &VirtualMachine) -> PyResult<usize> {
// TODO: audit functions first
let cache = library::libcache();
let mut cache_write = cache.write();
let lib_ref = cache_write.get_or_insert_lib(&name, vm).unwrap();
Ok(lib_ref.get_pointer())
}
#[pyfunction(name = "FreeLibrary")]
fn free_library(handle: usize) -> PyResult<()> {
let cache = library::libcache();
let mut cache_write = cache.write();
cache_write.drop_lib(handle);
Ok(())
}
#[pyfunction(name = "POINTER")]
pub fn pointer(_cls: PyTypeRef) {}
#[pyfunction]
pub fn pointer_fn(_inst: PyObjectRef) {}
#[cfg(target_os = "windows")]
#[pyfunction(name = "_check_HRESULT")]
pub fn check_hresult(_self: PyObjectRef, hr: i32, _vm: &VirtualMachine) -> PyResult<i32> {
// TODO: fixme
if hr < 0 {
// vm.ctx.new_windows_error(hr)
todo!();
} else {
Ok(hr)
}
}
#[pyfunction]
fn get_errno() -> i32 {
errno::errno().0
}
#[pyfunction]
fn set_errno(value: i32) {
errno::set_errno(errno::Errno(value));
}
}

View File

@@ -0,0 +1,5 @@
#[pyclass(name = "Array", module = "_ctypes")]
pub struct PyCArray {}
#[pyclass(flags(BASETYPE, IMMUTABLETYPE))]
impl PyCArray {}

View File

@@ -0,0 +1,224 @@
use crate::builtins::PyType;
use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyTypeRef};
use crate::convert::ToPyObject;
use crate::function::{Either, OptionalArg};
use crate::stdlib::ctypes::_ctypes::new_simple_type;
use crate::types::Constructor;
use crate::{AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine};
use crossbeam_utils::atomic::AtomicCell;
use num_traits::ToPrimitive;
use rustpython_common::lock::PyRwLock;
use std::fmt::Debug;
#[allow(dead_code)]
fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyResult {
match _type_ {
"c" => {
if value
.clone()
.downcast_exact::<PyBytes>(vm)
.is_ok_and(|v| v.len() == 1)
|| value
.clone()
.downcast_exact::<PyBytes>(vm)
.is_ok_and(|v| v.len() == 1)
|| value
.clone()
.downcast_exact::<PyInt>(vm)
.map_or(Ok(false), |v| {
let n = v.as_bigint().to_i64();
if let Some(n) = n {
Ok((0..=255).contains(&n))
} else {
Ok(false)
}
})?
{
Ok(value.clone())
} else {
Err(vm.new_type_error(
"one character bytes, bytearray or integer expected".to_string(),
))
}
}
"u" => {
if let Ok(b) = value.str(vm).map(|v| v.to_string().chars().count() == 1) {
if b {
Ok(value.clone())
} else {
Err(vm.new_type_error("one character unicode string expected".to_string()))
}
} else {
Err(vm.new_type_error(format!(
"unicode string expected instead of {} instance",
value.class().name()
)))
}
}
"b" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => {
if value.clone().downcast_exact::<PyInt>(vm).is_ok() {
Ok(value.clone())
} else {
Err(vm.new_type_error(format!(
"an integer is required (got type {})",
value.class().name()
)))
}
}
"f" | "d" | "g" => {
if value.clone().downcast_exact::<PyFloat>(vm).is_ok() {
Ok(value.clone())
} else {
Err(vm.new_type_error(format!("must be real number, not {}", value.class().name())))
}
}
"?" => Ok(PyObjectRef::from(
vm.ctx.new_bool(value.clone().try_to_bool(vm)?),
)),
"B" => {
if value.clone().downcast_exact::<PyInt>(vm).is_ok() {
Ok(vm.new_pyobj(u8::try_from_object(vm, value.clone())?))
} else {
Err(vm.new_type_error(format!("int expected instead of {}", value.class().name())))
}
}
"z" => {
if value.clone().downcast_exact::<PyInt>(vm).is_ok()
|| value.clone().downcast_exact::<PyBytes>(vm).is_ok()
{
Ok(value.clone())
} else {
Err(vm.new_type_error(format!(
"bytes or integer address expected instead of {} instance",
value.class().name()
)))
}
}
"Z" => {
if value.clone().downcast_exact::<PyStr>(vm).is_ok() {
Ok(value.clone())
} else {
Err(vm.new_type_error(format!(
"unicode string or integer address expected instead of {} instance",
value.class().name()
)))
}
}
_ => {
// "P"
if value.clone().downcast_exact::<PyInt>(vm).is_ok()
|| value.clone().downcast_exact::<PyNone>(vm).is_ok()
{
Ok(value.clone())
} else {
Err(vm.new_type_error("cannot be converted to pointer".to_string()))
}
}
}
}
pub struct RawBuffer {
#[allow(dead_code)]
pub inner: Box<[u8]>,
#[allow(dead_code)]
pub size: usize,
}
#[pyclass(name = "_CData", module = "_ctypes")]
pub struct PyCData {
_objects: AtomicCell<Vec<PyObjectRef>>,
_buffer: PyRwLock<RawBuffer>,
}
#[pyclass]
impl PyCData {}
#[pyclass(module = "_ctypes", name = "PyCSimpleType", base = "PyType")]
pub struct PySimpleMeta {}
#[pyclass(flags(BASETYPE))]
impl PySimpleMeta {
#[allow(clippy::new_ret_no_self)]
#[pymethod]
fn new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult {
Ok(PyObjectRef::from(
new_simple_type(Either::B(&cls), vm)?
.into_ref_with_type(vm, cls)?
.clone(),
))
}
}
#[pyclass(
name = "_SimpleCData",
base = "PyCData",
module = "_ctypes",
metaclass = "PySimpleMeta"
)]
#[derive(PyPayload)]
pub struct PyCSimple {
pub _type_: String,
pub value: AtomicCell<PyObjectRef>,
}
impl Debug for PyCSimple {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PyCSimple")
.field("_type_", &self._type_)
.finish()
}
}
impl Constructor for PyCSimple {
type Args = (OptionalArg,);
fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult {
let attributes = cls.get_attributes();
let _type_ = attributes
.iter()
.find(|(&k, _)| k.to_object().str(vm).unwrap().to_string() == *"_type_")
.unwrap()
.1
.str(vm)?
.to_string();
let value = if let Some(ref v) = args.0.into_option() {
set_primitive(_type_.as_str(), v, vm)?
} else {
match _type_.as_str() {
"c" | "u" => PyObjectRef::from(vm.ctx.new_bytes(vec![0])),
"b" | "B" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => {
PyObjectRef::from(vm.ctx.new_int(0))
}
"f" | "d" | "g" => PyObjectRef::from(vm.ctx.new_float(0.0)),
"?" => PyObjectRef::from(vm.ctx.new_bool(false)),
_ => vm.ctx.none(), // "z" | "Z" | "P"
}
};
Ok(PyCSimple {
_type_,
value: AtomicCell::new(value),
}
.to_pyobject(vm))
}
}
#[pyclass(flags(BASETYPE), with(Constructor))]
impl PyCSimple {
#[pygetset(name = "value")]
pub fn value(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
let zelf: &Py<Self> = instance
.downcast_ref()
.ok_or_else(|| vm.new_type_error("cannot get value of instance".to_string()))?;
Ok(unsafe { (*zelf.value.as_ptr()).clone() })
}
#[pygetset(name = "value", setter)]
fn set_value(instance: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
let zelf: PyRef<Self> = instance
.downcast()
.map_err(|_| vm.new_type_error("cannot set value of instance".to_string()))?;
let content = set_primitive(zelf._type_.as_str(), &value, vm)?;
zelf.value.store(content);
Ok(())
}
}

View File

@@ -0,0 +1,24 @@
use crate::stdlib::ctypes::PyCData;
use crate::PyObjectRef;
use crossbeam_utils::atomic::AtomicCell;
use rustpython_common::lock::PyRwLock;
use std::ffi::c_void;
#[derive(Debug)]
pub struct Function {
_pointer: *mut c_void,
_arguments: Vec<()>,
_return_type: Box<()>,
}
#[pyclass(module = "_ctypes", name = "CFuncPtr", base = "PyCData")]
pub struct PyCFuncPtr {
pub _name_: String,
pub _argtypes_: AtomicCell<Vec<PyObjectRef>>,
pub _restype_: AtomicCell<PyObjectRef>,
_handle: PyObjectRef,
_f: PyRwLock<Function>,
}
#[pyclass]
impl PyCFuncPtr {}

View File

@@ -0,0 +1,115 @@
use crate::VirtualMachine;
use crossbeam_utils::atomic::AtomicCell;
use libloading::Library;
use rustpython_common::lock::PyRwLock;
use std::collections::HashMap;
use std::ffi::c_void;
use std::fmt;
use std::ptr::null;
pub struct SharedLibrary {
lib: AtomicCell<Option<Library>>,
}
impl fmt::Debug for SharedLibrary {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "SharedLibrary")
}
}
impl SharedLibrary {
pub fn new(name: &str) -> Result<SharedLibrary, libloading::Error> {
Ok(SharedLibrary {
lib: AtomicCell::new(Some(unsafe { Library::new(name)? })),
})
}
#[allow(dead_code)]
pub fn get_sym(&self, name: &str) -> Result<*mut c_void, String> {
if let Some(inner) = unsafe { &*self.lib.as_ptr() } {
unsafe {
inner
.get(name.as_bytes())
.map(|f: libloading::Symbol<*mut c_void>| *f)
.map_err(|err| err.to_string())
}
} else {
Err("The library has been closed".to_string())
}
}
pub fn get_pointer(&self) -> usize {
if let Some(l) = unsafe { &*self.lib.as_ptr() } {
l as *const Library as usize
} else {
null::<c_void>() as usize
}
}
pub fn is_closed(&self) -> bool {
unsafe { &*self.lib.as_ptr() }.is_none()
}
pub fn close(&self) {
let old = self.lib.take();
self.lib.store(None);
drop(old);
}
}
impl Drop for SharedLibrary {
fn drop(&mut self) {
self.close();
}
}
pub struct ExternalLibs {
libraries: HashMap<usize, SharedLibrary>,
}
impl ExternalLibs {
pub fn new() -> Self {
Self {
libraries: HashMap::new(),
}
}
#[allow(dead_code)]
pub fn get_lib(&self, key: usize) -> Option<&SharedLibrary> {
self.libraries.get(&key)
}
pub fn get_or_insert_lib(
&mut self,
library_path: &str,
_vm: &VirtualMachine,
) -> Result<&SharedLibrary, libloading::Error> {
let nlib = SharedLibrary::new(library_path)?;
let key = nlib.get_pointer();
match self.libraries.get(&key) {
Some(l) => {
if l.is_closed() {
self.libraries.insert(key, nlib);
}
}
_ => {
self.libraries.insert(key, nlib);
}
};
Ok(self.libraries.get(&key).unwrap())
}
pub fn drop_lib(&mut self, key: usize) {
self.libraries.remove(&key);
}
}
rustpython_common::static_cell! {
static LIBCACHE: PyRwLock<ExternalLibs>;
}
pub fn libcache() -> &'static PyRwLock<ExternalLibs> {
LIBCACHE.get_or_init(|| PyRwLock::new(ExternalLibs::new()))
}

View File

@@ -0,0 +1,5 @@
#[pyclass(name = "Pointer", module = "_ctypes")]
pub struct PyCPointer {}
#[pyclass(flags(BASETYPE, IMMUTABLETYPE))]
impl PyCPointer {}

View File

@@ -0,0 +1,5 @@
#[pyclass(name = "Structure", module = "_ctypes")]
pub struct PyCStructure {}
#[pyclass(flags(BASETYPE, IMMUTABLETYPE))]
impl PyCStructure {}

View File

@@ -0,0 +1,5 @@
#[pyclass(name = "Union", module = "_ctypes")]
pub struct PyCUnion {}
#[pyclass(flags(BASETYPE, IMMUTABLETYPE))]
impl PyCUnion {}

View File

@@ -482,7 +482,7 @@ mod _io {
let size = size.to_usize();
let read = instance.get_attr("read", vm)?;
let mut res = Vec::new();
while size.map_or(true, |s| res.len() < s) {
while size.is_none_or(|s| res.len() < s) {
let read_res = ArgBytesLike::try_from_object(vm, read.call((1,), vm)?)?;
if read_res.with_ref(|b| b.is_empty()) {
break;

View File

@@ -37,6 +37,8 @@ pub mod posix;
#[path = "posix_compat.rs"]
pub mod posix;
#[cfg(any(target_family = "unix", target_family = "windows"))]
mod ctypes;
#[cfg(windows)]
pub(crate) mod msvcrt;
#[cfg(all(unix, not(any(target_os = "android", target_os = "redox"))))]
@@ -124,5 +126,9 @@ pub fn get_module_inits() -> StdlibMap {
"_winapi" => winapi::make_module,
"winreg" => winreg::make_module,
}
#[cfg(any(target_family = "unix", target_family = "windows"))]
{
"_ctypes" => ctypes::make_module,
}
}
}

View File

@@ -43,6 +43,12 @@ pub(crate) mod module {
|| attr & FileSystem::FILE_ATTRIBUTE_DIRECTORY != 0))
}
#[pyfunction]
pub(super) fn _supports_virtual_terminal() -> PyResult<bool> {
// TODO: implement this
Ok(true)
}
#[derive(FromArgs)]
pub(super) struct SymlinkArgs {
src: OsPath,

View File

@@ -978,7 +978,7 @@ pub(super) mod _os {
return Err(vm.new_value_error("negative argument not allowed".to_owned()));
}
let mut buf = vec![0u8; size as usize];
getrandom::getrandom(&mut buf).map_err(|e| match e.raw_os_error() {
getrandom::fill(&mut buf).map_err(|e| match e.raw_os_error() {
Some(errno) => io::Error::from_raw_os_error(errno).into_pyexception(vm),
None => vm.new_os_error("Getting random failed".to_owned()),
})?;

View File

@@ -43,6 +43,9 @@ mod decl {
DateTime, Datelike, Timelike,
};
use std::time::Duration;
#[cfg(target_env = "msvc")]
#[cfg(not(target_arch = "wasm32"))]
use windows::Win32::System::Time;
#[allow(dead_code)]
pub(super) const SEC_TO_MS: i64 = 1000;
@@ -152,6 +155,15 @@ mod decl {
Ok(get_perf_time(vm)?.as_nanos())
}
#[cfg(target_env = "msvc")]
#[cfg(not(target_arch = "wasm32"))]
fn get_tz_info() -> Time::TIME_ZONE_INFORMATION {
let mut info = Time::TIME_ZONE_INFORMATION::default();
let info_ptr = &mut info as *mut Time::TIME_ZONE_INFORMATION;
let _ = unsafe { Time::GetTimeZoneInformation(info_ptr) };
info
}
// #[pyfunction]
// fn tzset() {
// unsafe { super::_tzset() };
@@ -164,6 +176,15 @@ mod decl {
unsafe { super::c_timezone }
}
#[cfg(target_env = "msvc")]
#[cfg(not(target_arch = "wasm32"))]
#[pyattr]
fn timezone(_vm: &VirtualMachine) -> i32 {
let info = get_tz_info();
// https://users.rust-lang.org/t/accessing-tzname-and-similar-constants-in-windows/125771/3
(info.Bias + info.StandardBias) * 60
}
#[cfg(not(target_os = "freebsd"))]
#[cfg(not(target_env = "msvc"))]
#[cfg(not(target_arch = "wasm32"))]
@@ -172,6 +193,15 @@ mod decl {
unsafe { super::c_daylight }
}
#[cfg(target_env = "msvc")]
#[cfg(not(target_arch = "wasm32"))]
#[pyattr]
fn daylight(_vm: &VirtualMachine) -> i32 {
let info = get_tz_info();
// https://users.rust-lang.org/t/accessing-tzname-and-similar-constants-in-windows/125771/3
(info.StandardBias != info.DaylightBias) as i32
}
#[cfg(not(target_env = "msvc"))]
#[cfg(not(target_arch = "wasm32"))]
#[pyattr]
@@ -184,6 +214,22 @@ mod decl {
unsafe { (to_str(super::c_tzname[0]), to_str(super::c_tzname[1])) }.into_pytuple(vm)
}
#[cfg(target_env = "msvc")]
#[cfg(not(target_arch = "wasm32"))]
#[pyattr]
fn tzname(vm: &VirtualMachine) -> crate::builtins::PyTupleRef {
use crate::builtins::tuple::IntoPyTuple;
let info = get_tz_info();
let standard = widestring::decode_utf16_lossy(info.StandardName)
.filter(|&c| c != '\0')
.collect::<String>();
let daylight = widestring::decode_utf16_lossy(info.DaylightName)
.filter(|&c| c != '\0')
.collect::<String>();
let tz_name = (&*standard, &*daylight);
tz_name.into_pytuple(vm)
}
fn pyobj_to_date_time(
value: Either<f64, i64>,
vm: &VirtualMachine,

View File

@@ -75,18 +75,31 @@ pub(crate) mod _typing {
#[pyclass(name = "ParamSpec")]
#[derive(Debug, PyPayload)]
#[allow(dead_code)]
struct ParamSpec {}
pub(crate) struct ParamSpec {
name: PyObjectRef,
}
#[pyclass(flags(BASETYPE))]
impl ParamSpec {}
pub(crate) fn make_paramspec(name: PyObjectRef) -> ParamSpec {
ParamSpec { name }
}
#[pyattr]
#[pyclass(name = "TypeVarTuple")]
#[derive(Debug, PyPayload)]
#[allow(dead_code)]
pub(crate) struct TypeVarTuple {}
pub(crate) struct TypeVarTuple {
name: PyObjectRef,
}
#[pyclass(flags(BASETYPE))]
impl TypeVarTuple {}
pub(crate) fn make_typevartuple(name: PyObjectRef) -> TypeVarTuple {
TypeVarTuple { name }
}
#[pyattr]
#[pyclass(name = "ParamSpecArgs")]
#[derive(Debug, PyPayload)]

View File

@@ -28,10 +28,20 @@ mod _winapi {
ERROR_PIPE_CONNECTED, ERROR_SEM_TIMEOUT, GENERIC_READ, GENERIC_WRITE, STILL_ACTIVE,
WAIT_ABANDONED, WAIT_ABANDONED_0, WAIT_OBJECT_0, WAIT_TIMEOUT,
},
Globalization::{
LCMAP_FULLWIDTH, LCMAP_HALFWIDTH, LCMAP_HIRAGANA, LCMAP_KATAKANA,
LCMAP_LINGUISTIC_CASING, LCMAP_LOWERCASE, LCMAP_SIMPLIFIED_CHINESE, LCMAP_TITLECASE,
LCMAP_TRADITIONAL_CHINESE, LCMAP_UPPERCASE,
},
Storage::FileSystem::{
FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, FILE_GENERIC_READ,
FILE_GENERIC_WRITE, FILE_TYPE_CHAR, FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_REMOTE,
FILE_TYPE_UNKNOWN, OPEN_EXISTING, PIPE_ACCESS_DUPLEX, PIPE_ACCESS_INBOUND, SYNCHRONIZE,
COPYFILE2_CALLBACK_CHUNK_FINISHED, COPYFILE2_CALLBACK_CHUNK_STARTED,
COPYFILE2_CALLBACK_ERROR, COPYFILE2_CALLBACK_POLL_CONTINUE,
COPYFILE2_CALLBACK_STREAM_FINISHED, COPYFILE2_CALLBACK_STREAM_STARTED,
COPYFILE2_PROGRESS_CANCEL, COPYFILE2_PROGRESS_CONTINUE, COPYFILE2_PROGRESS_PAUSE,
COPYFILE2_PROGRESS_QUIET, COPYFILE2_PROGRESS_STOP, FILE_FLAG_FIRST_PIPE_INSTANCE,
FILE_FLAG_OVERLAPPED, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_TYPE_CHAR,
FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_REMOTE, FILE_TYPE_UNKNOWN, OPEN_EXISTING,
PIPE_ACCESS_DUPLEX, PIPE_ACCESS_INBOUND, SYNCHRONIZE,
},
System::{
Console::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE},
@@ -53,6 +63,13 @@ mod _winapi {
IDLE_PRIORITY_CLASS, INFINITE, NORMAL_PRIORITY_CLASS, PROCESS_DUP_HANDLE,
REALTIME_PRIORITY_CLASS, STARTF_USESHOWWINDOW, STARTF_USESTDHANDLES,
},
WindowsProgramming::{
COPY_FILE_ALLOW_DECRYPTED_DESTINATION, COPY_FILE_COPY_SYMLINK,
COPY_FILE_FAIL_IF_EXISTS, COPY_FILE_NO_BUFFERING, COPY_FILE_NO_OFFLOAD,
COPY_FILE_OPEN_SOURCE_FOR_WRITE, COPY_FILE_REQUEST_COMPRESSED_TRAFFIC,
COPY_FILE_REQUEST_SECURITY_PRIVILEGES, COPY_FILE_RESTARTABLE,
COPY_FILE_RESUME_FROM_PAUSE,
},
},
UI::WindowsAndMessaging::SW_HIDE,
};
@@ -119,6 +136,11 @@ mod _winapi {
Ok(HANDLE(target))
}
#[pyfunction]
fn GetACP() -> u32 {
unsafe { windows_sys::Win32::Globalization::GetACP() }
}
#[pyfunction]
fn GetCurrentProcess() -> HANDLE {
unsafe { windows::Win32::System::Threading::GetCurrentProcess() }
@@ -137,6 +159,16 @@ mod _winapi {
}
}
#[pyfunction]
fn GetLastError() -> u32 {
unsafe { windows_sys::Win32::Foundation::GetLastError() }
}
#[pyfunction]
fn GetVersion() -> u32 {
unsafe { windows_sys::Win32::System::SystemInformation::GetVersion() }
}
#[derive(FromArgs)]
struct CreateProcessArgs {
#[pyarg(positional)]
@@ -249,6 +281,21 @@ mod _winapi {
))
}
#[pyfunction]
fn OpenProcess(
desired_access: u32,
inherit_handle: bool,
process_id: u32,
) -> windows_sys::Win32::Foundation::HANDLE {
unsafe {
windows_sys::Win32::System::Threading::OpenProcess(
desired_access,
BOOL::from(inherit_handle),
process_id,
)
}
}
#[pyfunction]
fn NeedCurrentDirectoryForExePath(exe_name: PyStrRef) -> bool {
let exe_name = exe_name.as_str().to_wide_with_nul();
@@ -447,4 +494,24 @@ mod _winapi {
let (path, _) = path.split_at(length as usize);
Ok(String::from_utf16(path).unwrap())
}
#[pyfunction]
fn OpenMutexW(desired_access: u32, inherit_handle: bool, name: u16) -> PyResult<isize> {
let handle = unsafe {
windows_sys::Win32::System::Threading::OpenMutexW(
desired_access,
BOOL::from(inherit_handle),
windows_sys::core::PCWSTR::from(name as _),
)
};
// if handle.is_invalid() {
// return Err(errno_err(vm));
// }
Ok(handle)
}
#[pyfunction]
fn ReleaseMutex(handle: isize) -> WindowsSysResult<BOOL> {
WindowsSysResult(unsafe { windows_sys::Win32::System::Threading::ReleaseMutex(handle) })
}
}

View File

@@ -47,9 +47,13 @@ mod winreg {
// value types
#[pyattr]
pub use windows_sys::Win32::System::Registry::{
REG_BINARY, REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_DWORD_LITTLE_ENDIAN, REG_EXPAND_SZ,
REG_FULL_RESOURCE_DESCRIPTOR, REG_LINK, REG_MULTI_SZ, REG_NONE, REG_QWORD,
REG_QWORD_LITTLE_ENDIAN, REG_RESOURCE_LIST, REG_RESOURCE_REQUIREMENTS_LIST, REG_SZ,
REG_BINARY, REG_CREATED_NEW_KEY, REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_DWORD_LITTLE_ENDIAN,
REG_EXPAND_SZ, REG_FULL_RESOURCE_DESCRIPTOR, REG_LINK, REG_MULTI_SZ, REG_NONE,
REG_NOTIFY_CHANGE_ATTRIBUTES, REG_NOTIFY_CHANGE_LAST_SET, REG_NOTIFY_CHANGE_NAME,
REG_NOTIFY_CHANGE_SECURITY, REG_OPENED_EXISTING_KEY, REG_OPTION_BACKUP_RESTORE,
REG_OPTION_CREATE_LINK, REG_OPTION_NON_VOLATILE, REG_OPTION_OPEN_LINK, REG_OPTION_RESERVED,
REG_OPTION_VOLATILE, REG_QWORD, REG_QWORD_LITTLE_ENDIAN, REG_RESOURCE_LIST,
REG_RESOURCE_REQUIREMENTS_LIST, REG_SZ, REG_WHOLE_HIVE_VOLATILE,
};
#[pyattr]
@@ -98,7 +102,7 @@ mod winreg {
#[pymethod(magic)]
fn bool(&self) -> bool {
self.key().raw_handle() != 0
!self.key().raw_handle().is_null()
}
#[pymethod(magic)]
fn enter(zelf: PyRef<Self>) -> PyRef<Self> {

View File

@@ -4,9 +4,9 @@
use chrono::{prelude::DateTime, Local};
use std::time::{Duration, UNIX_EPOCH};
// = 3.12.0alpha
// = 3.13.0alpha
pub const MAJOR: usize = 3;
pub const MINOR: usize = 12;
pub const MINOR: usize = 13;
pub const MICRO: usize = 0;
pub const RELEASELEVEL: &str = "alpha";
pub const RELEASELEVEL_N: usize = 0xA;

View File

@@ -365,7 +365,15 @@ impl VirtualMachine {
let actual_class = obj.class();
let actual_type = &*actual_class.name();
let expected_type = &*class.name();
let msg = format!("Expected {msg} '{expected_type}' but '{actual_type}' found");
let msg = format!("Expected {msg} '{expected_type}' but '{actual_type}' found.");
#[cfg(debug_assertions)]
let msg = if class.get_id() == actual_class.get_id() {
let mut msg = msg;
msg += " Did you forget to add `#[pyclass(with(Constructor))]`?";
msg
} else {
msg
};
self.new_exception_msg(error_type.to_owned(), msg)
}

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.is_some_and(|slot_a| slot_a != slot_c)
&& slot_b.is_some_and(|slot_b| slot_b != slot_c)
if slot_a.is_some_and(|slot_a| !std::ptr::fn_addr_eq(slot_a, slot_c))
&& slot_b.is_some_and(|slot_b| !std::ptr::fn_addr_eq(slot_b, slot_c))
{
let ret = slot_c(a, b, c, self)?;
if !ret.is(&self.ctx.not_implemented) {

View File

@@ -10,7 +10,14 @@ rust-version.workspace = true
repository.workspace = true
license.workspace = true
[[bench]]
name = "benches"
harness = false
[dependencies]
num_enum = { workspace = true }
bitflags = { workspace = true }
optional = "0.5"
[dev-dependencies]
criterion = { workspace = true }

View File

@@ -1,11 +1,9 @@
#![feature(test)]
extern crate test;
use test::Bencher;
use rustpython_sre_engine::{Request, State, StrDrive};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
struct Pattern {
pattern: &'static str,
code: &'static [u32],
}
@@ -25,52 +23,51 @@ impl Pattern {
}
}
#[bench]
fn benchmarks(b: &mut Bencher) {
fn basic(c: &mut Criterion) {
// # test common prefix
// pattern p1 = re.compile('Python|Perl') # , 'Perl'), # Alternation
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p1 = Pattern { code: &[14, 8, 1, 4, 6, 1, 1, 80, 0, 16, 80, 7, 13, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 11, 9, 16, 101, 16, 114, 16, 108, 15, 2, 0, 1] };
#[rustfmt::skip] let p1 = Pattern { pattern: "Python|Perl", code: &[14, 8, 1, 4, 6, 1, 1, 80, 0, 16, 80, 7, 13, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 11, 9, 16, 101, 16, 114, 16, 108, 15, 2, 0, 1] };
// END GENERATED
// pattern p2 = re.compile('(Python|Perl)') #, 'Perl'), # Grouped alternation
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p2 = Pattern { code: &[14, 8, 1, 4, 6, 1, 0, 80, 0, 17, 0, 16, 80, 7, 13, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 11, 9, 16, 101, 16, 114, 16, 108, 15, 2, 0, 17, 1, 1] };
#[rustfmt::skip] let p2 = Pattern { pattern: "(Python|Perl)", code: &[14, 8, 1, 4, 6, 1, 0, 80, 0, 17, 0, 16, 80, 7, 13, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 11, 9, 16, 101, 16, 114, 16, 108, 15, 2, 0, 17, 1, 1] };
// END GENERATED
// pattern p3 = re.compile('Python|Perl|Tcl') #, 'Perl'), # Alternation
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p3 = Pattern { code: &[14, 9, 4, 3, 6, 16, 80, 16, 84, 0, 7, 15, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 22, 11, 16, 80, 16, 101, 16, 114, 16, 108, 15, 11, 9, 16, 84, 16, 99, 16, 108, 15, 2, 0, 1] };
#[rustfmt::skip] let p3 = Pattern { pattern: "Python|Perl|Tcl", code: &[14, 9, 4, 3, 6, 16, 80, 16, 84, 0, 7, 15, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 22, 11, 16, 80, 16, 101, 16, 114, 16, 108, 15, 11, 9, 16, 84, 16, 99, 16, 108, 15, 2, 0, 1] };
// END GENERATED
// pattern p4 = re.compile('(Python|Perl|Tcl)') #, 'Perl'), # Grouped alternation
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p4 = Pattern { code: &[14, 9, 4, 3, 6, 16, 80, 16, 84, 0, 17, 0, 7, 15, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 22, 11, 16, 80, 16, 101, 16, 114, 16, 108, 15, 11, 9, 16, 84, 16, 99, 16, 108, 15, 2, 0, 17, 1, 1] };
#[rustfmt::skip] let p4 = Pattern { pattern: "(Python|Perl|Tcl)", code: &[14, 9, 4, 3, 6, 16, 80, 16, 84, 0, 17, 0, 7, 15, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 22, 11, 16, 80, 16, 101, 16, 114, 16, 108, 15, 11, 9, 16, 84, 16, 99, 16, 108, 15, 2, 0, 17, 1, 1] };
// END GENERATED
// pattern p5 = re.compile('(Python)\\1') #, 'PythonPython'), # Backreference
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p5 = Pattern { code: &[14, 18, 1, 12, 12, 6, 0, 80, 121, 116, 104, 111, 110, 0, 0, 0, 0, 0, 0, 17, 0, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 17, 1, 11, 0, 1] };
#[rustfmt::skip] let p5 = Pattern { pattern: "(Python)\\1", code: &[14, 18, 1, 12, 12, 6, 0, 80, 121, 116, 104, 111, 110, 0, 0, 0, 0, 0, 0, 17, 0, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 17, 1, 11, 0, 1] };
// END GENERATED
// pattern p6 = re.compile('([0a-z][a-z0-9]*,)+') #, 'a5,b7,c9,'), # Disable the fastmap optimization
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p6 = Pattern { code: &[14, 4, 0, 2, 4294967295, 23, 31, 1, 4294967295, 17, 0, 13, 7, 16, 48, 22, 97, 122, 0, 24, 13, 0, 4294967295, 13, 8, 22, 97, 122, 22, 48, 57, 0, 1, 16, 44, 17, 1, 18, 1] };
#[rustfmt::skip] let p6 = Pattern { pattern: "([0a-z][a-z0-9]*,)+", code: &[14, 4, 0, 2, 4294967295, 23, 31, 1, 4294967295, 17, 0, 13, 7, 16, 48, 22, 97, 122, 0, 24, 13, 0, 4294967295, 13, 8, 22, 97, 122, 22, 48, 57, 0, 1, 16, 44, 17, 1, 18, 1] };
// END GENERATED
// pattern p7 = re.compile('([a-z][a-z0-9]*,)+') #, 'a5,b7,c9,'), # A few sets
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p7 = Pattern { code: &[14, 4, 0, 2, 4294967295, 23, 29, 1, 4294967295, 17, 0, 13, 5, 22, 97, 122, 0, 24, 13, 0, 4294967295, 13, 8, 22, 97, 122, 22, 48, 57, 0, 1, 16, 44, 17, 1, 18, 1] };
#[rustfmt::skip] let p7 = Pattern { pattern: "([a-z][a-z0-9]*,)+", code: &[14, 4, 0, 2, 4294967295, 23, 29, 1, 4294967295, 17, 0, 13, 5, 22, 97, 122, 0, 24, 13, 0, 4294967295, 13, 8, 22, 97, 122, 22, 48, 57, 0, 1, 16, 44, 17, 1, 18, 1] };
// END GENERATED
// pattern p8 = re.compile('Python') #, 'Python'), # Simple text literal
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p8 = Pattern { code: &[14, 18, 3, 6, 6, 6, 6, 80, 121, 116, 104, 111, 110, 0, 0, 0, 0, 0, 0, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 1] };
#[rustfmt::skip] let p8 = Pattern { pattern: "Python", code: &[14, 18, 3, 6, 6, 6, 6, 80, 121, 116, 104, 111, 110, 0, 0, 0, 0, 0, 0, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 1] };
// END GENERATED
// pattern p9 = re.compile('.*Python') #, 'Python'), # Bad text literal
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p9 = Pattern { code: &[14, 4, 0, 6, 4294967295, 24, 5, 0, 4294967295, 2, 1, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 1] };
#[rustfmt::skip] let p9 = Pattern { pattern: ".*Python", code: &[14, 4, 0, 6, 4294967295, 24, 5, 0, 4294967295, 2, 1, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 1] };
// END GENERATED
// pattern p10 = re.compile('.*Python.*') #, 'Python'), # Worse text literal
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p10 = Pattern { code: &[14, 4, 0, 6, 4294967295, 24, 5, 0, 4294967295, 2, 1, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 24, 5, 0, 4294967295, 2, 1, 1] };
#[rustfmt::skip] let p10 = Pattern { pattern: ".*Python.*", code: &[14, 4, 0, 6, 4294967295, 24, 5, 0, 4294967295, 2, 1, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 24, 5, 0, 4294967295, 2, 1, 1] };
// END GENERATED
// pattern p11 = re.compile('.*(Python)') #, 'Python'), # Bad text literal with grouping
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p11 = Pattern { code: &[14, 4, 0, 6, 4294967295, 24, 5, 0, 4294967295, 2, 1, 17, 0, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 17, 1, 1] };
#[rustfmt::skip] let p11 = Pattern { pattern: ".*(Python)", code: &[14, 4, 0, 6, 4294967295, 24, 5, 0, 4294967295, 2, 1, 17, 0, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 17, 1, 1] };
// END GENERATED
let tests = [
@@ -87,25 +84,33 @@ fn benchmarks(b: &mut Bencher) {
(p11, "Python"),
];
b.iter(move || {
for (p, s) in &tests {
let (req, mut state) = p.state(s.clone());
assert!(state.search(req));
let (req, mut state) = p.state(s.clone());
assert!(state.pymatch(&req));
let (mut req, mut state) = p.state(s.clone());
req.match_all = true;
assert!(state.pymatch(&req));
let s2 = format!("{}{}{}", " ".repeat(10000), s, " ".repeat(10000));
let (req, mut state) = p.state_range(s2.as_str(), 0..usize::MAX);
assert!(state.search(req));
let (req, mut state) = p.state_range(s2.as_str(), 10000..usize::MAX);
assert!(state.pymatch(&req));
let (req, mut state) = p.state_range(s2.as_str(), 10000..10000 + s.len());
assert!(state.pymatch(&req));
let (mut req, mut state) = p.state_range(s2.as_str(), 10000..10000 + s.len());
req.match_all = true;
assert!(state.pymatch(&req));
}
})
let mut group = c.benchmark_group("basic");
for (p, s) in tests {
group.bench_with_input(BenchmarkId::new(p.pattern, s), s, |b, s| {
b.iter(|| {
let (req, mut state) = p.state(s);
assert!(state.search(req));
let (req, mut state) = p.state(s);
assert!(state.pymatch(&req));
let (mut req, mut state) = p.state(s);
req.match_all = true;
assert!(state.pymatch(&req));
let s2 = format!("{}{}{}", " ".repeat(10000), s, " ".repeat(10000));
let (req, mut state) = p.state_range(s2.as_str(), 0..usize::MAX);
assert!(state.search(req));
let (req, mut state) = p.state_range(s2.as_str(), 10000..usize::MAX);
assert!(state.pymatch(&req));
let (req, mut state) = p.state_range(s2.as_str(), 10000..10000 + s.len());
assert!(state.pymatch(&req));
let (mut req, mut state) = p.state_range(s2.as_str(), 10000..10000 + s.len());
req.match_all = true;
assert!(state.pymatch(&req));
});
});
}
}
criterion_group!(benches, basic);
criterion_main!(benches);

View File

@@ -1,9 +1,6 @@
import os
from pathlib import Path
import re
import sre_constants
import sre_compile
import sre_parse
import json
from itertools import chain
@@ -11,13 +8,13 @@ m = re.search(r"const SRE_MAGIC: usize = (\d+);", open("src/constants.rs").read(
sre_engine_magic = int(m.group(1))
del m
assert sre_constants.MAGIC == sre_engine_magic
assert re._constants.MAGIC == sre_engine_magic
class CompiledPattern:
@classmethod
def compile(cls, pattern, flags=0):
p = sre_parse.parse(pattern)
code = sre_compile._code(p, flags)
p = re._parser.parse(pattern)
code = re._compiler._code(p, flags)
self = cls()
self.pattern = pattern
self.code = code
@@ -28,12 +25,32 @@ for k, v in re.RegexFlag.__members__.items():
setattr(CompiledPattern, k, v)
class EscapeRustStr:
hardcoded = {
ord('\r'): '\\r',
ord('\t'): '\\t',
ord('\r'): '\\r',
ord('\n'): '\\n',
ord('\\'): '\\\\',
ord('\''): '\\\'',
ord('\"'): '\\\"',
}
@classmethod
def __class_getitem__(cls, ch):
if (rpl := cls.hardcoded.get(ch)) is not None:
return rpl
if ch in range(0x20, 0x7f):
return ch
return f"\\u{{{ch:x}}}"
def rust_str(s):
return '"' + s.translate(EscapeRustStr) + '"'
# matches `// pattern {varname} = re.compile(...)`
pattern_pattern = re.compile(r"^((\s*)\/\/\s*pattern\s+(\w+)\s+=\s+(.+?))$(?:.+?END GENERATED)?", re.M | re.S)
def replace_compiled(m):
line, indent, varname, pattern = m.groups()
pattern = eval(pattern, {"re": CompiledPattern})
pattern = f"Pattern {{ code: &{json.dumps(pattern.code)} }}"
pattern = f"Pattern {{ pattern: {rust_str(pattern.pattern)}, code: &{json.dumps(pattern.code)} }}"
return f'''{line}
{indent}// START GENERATED by generate_tests.py
{indent}#[rustfmt::skip] let {varname} = {pattern};

View File

@@ -181,7 +181,7 @@ unsafe fn next_code_point(ptr: &mut *const u8) -> u32 {
let z = **ptr;
*ptr = ptr.offset(1);
let y_z = utf8_acc_cont_byte((y & CONT_MASK) as u32, z);
ch = init << 12 | y_z;
ch = (init << 12) | y_z;
if x >= 0xF0 {
// [x y z w] case
// use only the lower 3 bits of `init`
@@ -189,7 +189,7 @@ unsafe fn next_code_point(ptr: &mut *const u8) -> u32 {
// so the iterator must produce a value here.
let w = **ptr;
*ptr = ptr.offset(1);
ch = (init & 7) << 18 | utf8_acc_cont_byte(y_z, w);
ch = ((init & 7) << 18) | utf8_acc_cont_byte(y_z, w);
}
}

View File

@@ -1,6 +1,7 @@
use rustpython_sre_engine::{Request, State, StrDrive};
struct Pattern {
pattern: &'static str,
code: &'static [u32],
}
@@ -16,7 +17,7 @@ impl Pattern {
fn test_2427() {
// pattern lookbehind = re.compile(r'(?<!\.)x\b')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let lookbehind = Pattern { code: &[14, 4, 0, 1, 1, 5, 5, 1, 16, 46, 1, 16, 120, 6, 10, 1] };
#[rustfmt::skip] let lookbehind = Pattern { pattern: "(?<!\\.)x\\b", code: &[14, 4, 0, 1, 1, 5, 5, 1, 16, 46, 1, 16, 120, 6, 10, 1] };
// END GENERATED
let (req, mut state) = lookbehind.state("x");
assert!(state.pymatch(&req));
@@ -26,7 +27,7 @@ fn test_2427() {
fn test_assert() {
// pattern positive_lookbehind = re.compile(r'(?<=abc)def')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let positive_lookbehind = Pattern { code: &[14, 4, 0, 3, 3, 4, 9, 3, 16, 97, 16, 98, 16, 99, 1, 16, 100, 16, 101, 16, 102, 1] };
#[rustfmt::skip] let positive_lookbehind = Pattern { pattern: "(?<=abc)def", code: &[14, 4, 0, 3, 3, 4, 9, 3, 16, 97, 16, 98, 16, 99, 1, 16, 100, 16, 101, 16, 102, 1] };
// END GENERATED
let (req, mut state) = positive_lookbehind.state("abcdef");
assert!(state.search(req));
@@ -36,7 +37,7 @@ fn test_assert() {
fn test_string_boundaries() {
// pattern big_b = re.compile(r'\B')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let big_b = Pattern { code: &[14, 4, 0, 0, 0, 6, 11, 1] };
#[rustfmt::skip] let big_b = Pattern { pattern: "\\B", code: &[14, 4, 0, 0, 0, 6, 11, 1] };
// END GENERATED
let (req, mut state) = big_b.state("");
assert!(!state.search(req));
@@ -46,7 +47,7 @@ fn test_string_boundaries() {
fn test_zerowidth() {
// pattern p = re.compile(r'\b|:+')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 0, 4294967295, 7, 5, 6, 10, 15, 12, 10, 24, 6, 1, 4294967295, 16, 58, 1, 15, 2, 0, 1] };
#[rustfmt::skip] let p = Pattern { pattern: "\\b|:+", code: &[14, 4, 0, 0, 4294967295, 7, 5, 6, 10, 15, 12, 10, 24, 6, 1, 4294967295, 16, 58, 1, 15, 2, 0, 1] };
// END GENERATED
let (mut req, mut state) = p.state("a:");
req.must_advance = true;
@@ -59,7 +60,7 @@ fn test_repeat_context_panic() {
use optional::Optioned;
// pattern p = re.compile(r'(?:a*?(xx)??z)*')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 0, 4294967295, 23, 25, 0, 4294967295, 26, 6, 0, 4294967295, 16, 97, 1, 23, 11, 0, 1, 17, 0, 16, 120, 16, 120, 17, 1, 19, 16, 122, 18, 1] };
#[rustfmt::skip] let p = Pattern { pattern: "(?:a*?(xx)??z)*", code: &[14, 4, 0, 0, 4294967295, 23, 25, 0, 4294967295, 26, 6, 0, 4294967295, 16, 97, 1, 23, 11, 0, 1, 17, 0, 16, 120, 16, 120, 17, 1, 19, 16, 122, 18, 1] };
// END GENERATED
let (req, mut state) = p.state("axxzaz");
assert!(state.pymatch(&req));
@@ -73,7 +74,7 @@ fn test_repeat_context_panic() {
fn test_double_max_until() {
// pattern p = re.compile(r'((1)?)*')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 0, 4294967295, 23, 18, 0, 4294967295, 17, 0, 23, 9, 0, 1, 17, 2, 16, 49, 17, 3, 18, 17, 1, 18, 1] };
#[rustfmt::skip] let p = Pattern { pattern: "((1)?)*", code: &[14, 4, 0, 0, 4294967295, 23, 18, 0, 4294967295, 17, 0, 23, 9, 0, 1, 17, 2, 16, 49, 17, 3, 18, 17, 1, 18, 1] };
// END GENERATED
let (req, mut state) = p.state("1111");
assert!(state.pymatch(&req));
@@ -84,7 +85,7 @@ fn test_double_max_until() {
fn test_info_single() {
// pattern p = re.compile(r'aa*')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { code: &[14, 8, 1, 1, 4294967295, 1, 1, 97, 0, 16, 97, 24, 6, 0, 4294967295, 16, 97, 1, 1] };
#[rustfmt::skip] let p = Pattern { pattern: "aa*", code: &[14, 8, 1, 1, 4294967295, 1, 1, 97, 0, 16, 97, 24, 6, 0, 4294967295, 16, 97, 1, 1] };
// END GENERATED
let (req, mut state) = p.state("baaaa");
assert!(state.search(req));
@@ -96,7 +97,7 @@ fn test_info_single() {
fn test_info_single2() {
// pattern p = re.compile(r'Python|Perl')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { code: &[14, 8, 1, 4, 6, 1, 1, 80, 0, 16, 80, 7, 13, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 11, 9, 16, 101, 16, 114, 16, 108, 15, 2, 0, 1] };
#[rustfmt::skip] let p = Pattern { pattern: "Python|Perl", code: &[14, 8, 1, 4, 6, 1, 1, 80, 0, 16, 80, 7, 13, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 11, 9, 16, 101, 16, 114, 16, 108, 15, 2, 0, 1] };
// END GENERATED
let (req, mut state) = p.state("Perl");
assert!(state.search(req));
@@ -106,7 +107,7 @@ fn test_info_single2() {
fn test_info_literal() {
// pattern p = re.compile(r'ababc+')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { code: &[14, 14, 1, 5, 4294967295, 4, 4, 97, 98, 97, 98, 0, 0, 1, 2, 16, 97, 16, 98, 16, 97, 16, 98, 24, 6, 1, 4294967295, 16, 99, 1, 1] };
#[rustfmt::skip] let p = Pattern { pattern: "ababc+", code: &[14, 14, 1, 5, 4294967295, 4, 4, 97, 98, 97, 98, 0, 0, 1, 2, 16, 97, 16, 98, 16, 97, 16, 98, 24, 6, 1, 4294967295, 16, 99, 1, 1] };
// END GENERATED
let (req, mut state) = p.state("!ababc");
assert!(state.search(req));
@@ -116,7 +117,7 @@ fn test_info_literal() {
fn test_info_literal2() {
// pattern p = re.compile(r'(python)\1')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { code: &[14, 18, 1, 12, 12, 6, 0, 112, 121, 116, 104, 111, 110, 0, 0, 0, 0, 0, 0, 17, 0, 16, 112, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 17, 1, 11, 0, 1] };
#[rustfmt::skip] let p = Pattern { pattern: "(python)\\1", code: &[14, 18, 1, 12, 12, 6, 0, 112, 121, 116, 104, 111, 110, 0, 0, 0, 0, 0, 0, 17, 0, 16, 112, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 17, 1, 11, 0, 1] };
// END GENERATED
let (req, mut state) = p.state("pythonpython");
assert!(state.search(req));
@@ -126,7 +127,7 @@ fn test_info_literal2() {
fn test_repeat_in_assertions() {
// pattern p = re.compile('^([ab]*?)(?=(b)?)c', re.IGNORECASE)
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 1, 4294967295, 6, 0, 17, 0, 26, 10, 0, 4294967295, 39, 5, 22, 97, 98, 0, 1, 17, 1, 4, 14, 0, 23, 9, 0, 1, 17, 2, 40, 98, 17, 3, 18, 1, 40, 99, 1] };
#[rustfmt::skip] let p = Pattern { pattern: "^([ab]*?)(?=(b)?)c", code: &[14, 4, 0, 1, 4294967295, 6, 0, 17, 0, 26, 10, 0, 4294967295, 39, 5, 22, 97, 98, 0, 1, 17, 1, 4, 14, 0, 23, 9, 0, 1, 17, 2, 40, 98, 17, 3, 18, 1, 40, 99, 1] };
// END GENERATED
let (req, mut state) = p.state("abc");
assert!(state.search(req));
@@ -136,7 +137,7 @@ fn test_repeat_in_assertions() {
fn test_possessive_quantifier() {
// pattern p = re.compile('e++a')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 2, 4294967295, 29, 6, 1, 4294967295, 16, 101, 1, 16, 97, 1] };
#[rustfmt::skip] let p = Pattern { pattern: "e++a", code: &[14, 4, 0, 2, 4294967295, 29, 6, 1, 4294967295, 16, 101, 1, 16, 97, 1] };
// END GENERATED
let (req, mut state) = p.state("eeea");
assert!(state.pymatch(&req));
@@ -146,7 +147,7 @@ fn test_possessive_quantifier() {
fn test_possessive_atomic_group() {
// pattern p = re.compile('(?>x)++x')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 2, 4294967295, 28, 8, 1, 4294967295, 27, 4, 16, 120, 1, 1, 16, 120, 1] };
#[rustfmt::skip] let p = Pattern { pattern: "(?>x)++x", code: &[14, 4, 0, 2, 4294967295, 28, 8, 1, 4294967295, 27, 4, 16, 120, 1, 1, 16, 120, 1] };
// END GENERATED
let (req, mut state) = p.state("xxx");
assert!(!state.pymatch(&req));
@@ -156,7 +157,7 @@ fn test_possessive_atomic_group() {
fn test_bug_20998() {
// pattern p = re.compile('[a-c]+', re.I)
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 1, 4294967295, 24, 10, 1, 4294967295, 39, 5, 22, 97, 99, 0, 1, 1] };
#[rustfmt::skip] let p = Pattern { pattern: "[a-c]+", code: &[14, 4, 0, 1, 4294967295, 24, 10, 1, 4294967295, 39, 5, 22, 97, 99, 0, 1, 1] };
// END GENERATED
let (mut req, mut state) = p.state("ABC");
req.match_all = true;
@@ -168,7 +169,7 @@ fn test_bug_20998() {
fn test_bigcharset() {
// pattern p = re.compile('[a-z]*', re.I)
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 0, 4294967295, 24, 97, 0, 4294967295, 39, 92, 10, 3, 33685760, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 0, 0, 0, 134217726, 0, 0, 0, 0, 0, 131072, 0, 2147483648, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] };
#[rustfmt::skip] let p = Pattern { pattern: "[a-z]*", code: &[14, 4, 0, 0, 4294967295, 24, 97, 0, 4294967295, 39, 92, 10, 3, 33685760, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 0, 0, 0, 134217726, 0, 0, 0, 0, 0, 131072, 0, 2147483648, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] };
// END GENERATED
let (req, mut state) = p.state("x ");
assert!(state.pymatch(&req));
@@ -178,4 +179,7 @@ fn test_bigcharset() {
#[test]
fn test_search_nonascii() {
// pattern p = re.compile('\xe0+')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { pattern: "\u{e0}+", code: &[14, 4, 0, 1, 4294967295, 24, 6, 1, 4294967295, 16, 224, 1, 1] };
// END GENERATED
}

View File

@@ -0,0 +1,5 @@
[build]
target = "wasm32-unknown-unknown"
[target.wasm32-unknown-unknown]
rustflags = ["--cfg=getrandom_backend=\"wasm_js\""]

View File

@@ -28,6 +28,9 @@ rustpython-parser = { workspace = true }
serde = { workspace = true }
wasm-bindgen = { workspace = true }
# remove once getrandom 0.2 is no longer otherwise in the dependency tree
getrandom = { version = "0.2", features = ["js"] }
console_error_panic_hook = "0.1"
js-sys = "0.3"
serde-wasm-bindgen = "0.3.1"
@@ -47,4 +50,4 @@ web-sys = { version = "0.3", features = [
wasm-opt = false#["-O1"]
[lints]
workspace = true
workspace = true

View File

@@ -0,0 +1,5 @@
[build]
target = "wasm32-unknown-unknown"
[target.wasm32-unknown-unknown]
rustflags = ["--cfg=getrandom_backend=\"custom\""]

View File

@@ -8,6 +8,7 @@ crate-type = ["cdylib"]
[dependencies]
getrandom = { version = "0.2.12", features = ["custom"] }
getrandom_03 = { package = "getrandom", version = "0.3" }
rustpython-vm = { path = "../../vm", default-features = false, features = ["compiler"] }
[workspace]

View File

@@ -14,3 +14,11 @@ fn getrandom_always_fail(_buf: &mut [u8]) -> Result<(), getrandom::Error> {
}
getrandom::register_custom_getrandom!(getrandom_always_fail);
#[unsafe(no_mangle)]
unsafe extern "Rust" fn __getrandom_v03_custom(
_dest: *mut u8,
_len: usize,
) -> Result<(), getrandom_03::Error> {
Err(getrandom_03::Error::UNSUPPORTED)
}

View File

@@ -35,8 +35,8 @@ GENERATED_FILE = "extra_tests/not_impl.py"
implementation = platform.python_implementation()
if implementation != "CPython":
sys.exit(f"whats_left.py must be run under CPython, got {implementation} instead")
if sys.version_info[:2] < (3, 12):
sys.exit(f"whats_left.py must be run under CPython 3.12 or newer, got {implementation} {sys.version} instead")
if sys.version_info[:2] < (3, 13):
sys.exit(f"whats_left.py must be run under CPython 3.13 or newer, got {implementation} {sys.version} instead")
def parse_args():
parser = argparse.ArgumentParser(description="Process some integers.")