Compare commits

...

398 Commits

Author SHA1 Message Date
dependabot[bot]
2c97de2528 Bump the crypto group across 1 directory with 6 updates
Bumps the crypto group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [digest](https://github.com/RustCrypto/traits) | `0.10.7` | `0.11.3` |
| [hmac](https://github.com/RustCrypto/MACs) | `0.12.1` | `0.13.0` |
| [md-5](https://github.com/RustCrypto/hashes) | `0.10.6` | `0.11.0` |
| [pbkdf2](https://github.com/RustCrypto/password-hashes) | `0.12.2` | `0.13.0` |
| [sha2](https://github.com/RustCrypto/hashes) | `0.10.9` | `0.11.0` |
| [sha3](https://github.com/RustCrypto/hashes) | `0.10.9` | `0.12.0` |



Updates `digest` from 0.10.7 to 0.11.3
- [Commits](https://github.com/RustCrypto/traits/compare/digest-v0.10.7...digest-v0.11.3)

Updates `hmac` from 0.12.1 to 0.13.0
- [Commits](https://github.com/RustCrypto/MACs/compare/hmac-v0.12.1...hmac-v0.13.0)

Updates `md-5` from 0.10.6 to 0.11.0
- [Commits](https://github.com/RustCrypto/hashes/compare/md-5-v0.10.6...md2-v0.11.0)

Updates `pbkdf2` from 0.12.2 to 0.13.0
- [Commits](https://github.com/RustCrypto/password-hashes/compare/pbkdf2-v0.12.2...pbkdf2-v0.13.0)

Updates `sha2` from 0.10.9 to 0.11.0
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.10.9...sha2-v0.11.0)

Updates `sha3` from 0.10.9 to 0.12.0
- [Commits](https://github.com/RustCrypto/hashes/compare/sha3-v0.10.9...sha3-v0.12.0)

---
updated-dependencies:
- dependency-name: digest
  dependency-version: 0.11.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
- dependency-name: hmac
  dependency-version: 0.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
- dependency-name: md-5
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
- dependency-name: pbkdf2
  dependency-version: 0.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
- dependency-name: sha2
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
- dependency-name: sha3
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 02:11:44 +00:00
Joshua Megnauth
30ae48b24b Support custom rustls providers (#7657)
* Modularize rustls as work towards providers

`rustls`'s architecture is very clean and trait-driven. There are many
providers for `rustls` including the built-in `aws-lc-rs` and `ring` as
well as backends for `boringssl`, `graviola`, `openssl`, `mbedtls`, etc.

This commit removes the hard dependency on `aws-lc-rs` and adds support
for `ring`. It works towards #7059 as well.

* Clean up rustls features

* Remove ring as an explicit feature
* ssl-rustls is the default and implies aws-lc

* Support custom rustls crypto providers

The new feature, `ssl-rustls-no-provider`, enables custom rustls
providers. By default, `aws-lc-rs` is enabled which matches the old
behavior and keeps backward compatibility.

I wrote a new type that abstracts what we need from crypto providers.
CryptoExt encapsulates the ticketer as well as cipher suites and KX
groups. I wrote fallbacks to help select a reasonable default if a
provider is missing features (they all seem to support the same things
though).

I also wrote an example to show how to actually use custom providers.

* Fix duplicate VERIFY_X509 constants and unused imports in ssl module

Remove duplicate VERIFY_X509_STRICT/VERIFY_X509_PARTIAL_CHAIN definitions
from compat.rs (already defined in _ssl module with #[pyattr]).
Remove unused imports: ClientConnection, ServerConnection.

* no-provider as default

* Fix CI failures: openssl build, wasm target, cargo-shear

- Update openssl.rs to use renamed sock_wait/SockWaitKind and add vm parameter
- Add skip_ssl for wasm32-wasip2 target (aws-lc-sys cannot build for wasm)
- Remove unused workspace dependency aws-lc-rs
- Fix foreign-types-shared version to match openssl's dependency (0.1)
- Restore Cargo.lock from upstream/main

---------

Co-authored-by: Jeong, YunWon <jeong@youknowone.org>
2026-05-28 11:07:09 +09:00
Jeong, YunWon
1a959cf7f3 Align codegen passes and opcode metadata with CPython (#7987)
* Share marshal ref table between code object and its internals

read_marshal_bytes, _str, _str_vec, _name_tuple, and _const_tuple now
take a shared ref table and resolve TYPE_REF / register FLAG_REF
entries. deserialize_code is split into a public wrapper and an inner
function that receives the ref table; deserialize_value_depth opens a
fresh inner ref space when it hits Type::Code, mirroring CPython's
behaviour of putting the code object itself at ref slot 0. Nested code
objects inside const tuples reuse the surrounding code's ref space via
the new read_const_value helper.

* Align PYC magic number, FORMAT_VERSION, and header check with CPython 3.14

PYC_MAGIC_NUMBER changes from 2994 to 3627, matching CPython 3.14's
pyc_magic_number_token (0x0a0d0e2b). marshal FORMAT_VERSION drops from
5 to 4 (the encoder/marshal.version value; the decoder already accepts
both). check_pyc_magic_number_bytes now compares all four magic bytes
instead of the first two.

* Accept CPython-tagged .pyc as read-only bytecode source

SourceFileLoader.get_code now also looks for .pyc files using
_RP_FALLBACK_CACHE_TAGS (currently ('cpython-314',)) in addition to
sys.implementation.cache_tag. The matched .pyc is only used for
reading; recompilation still writes to the RustPython-tagged path, so
CPython's .pyc is never overwritten. Source-stat / hash / timestamp
validation logic is unchanged.

* Apply rustfmt to marshal helpers

* Marshal PySlice from format version 4 instead of 5

CPython's marshal supports TYPE_SLICE from format version 4 onwards
and that is the default version. Rejecting slice dumps below version
5 made marshal.dumps(slice(...)) fail with the default version and
broke test.test_marshal.SliceTestCase.test_slice.

* Revert "Accept CPython-tagged .pyc as read-only bytecode source"

Lib/importlib/_bootstrap_external.py is CPython's own code copied
verbatim; local patches here defeat compatibility tracking. The
cpython-XX cache_tag fallback needs to live on the RustPython side
(Rust code or sys.implementation.cache_tag policy), not as edits to
the imported standard library.

This reverts commit 1fc426d0fb5fcdb50d35cad13bbb43e8f6ce1c7f.

* Set marshal FORMAT_VERSION to 5 to match CPython 3.14.5

Py_MARSHAL_VERSION is 5 in CPython 3.14.5 (Include/marshal.h:16) and
TYPE_SLICE serialization rejects version < 5 (Python/marshal.c:720).
Restore the same threshold and constant so marshal.version and the
slice-marshal gate match CPython.

* Thread marshal recursion depth through nested code objects

Code objects embedded in const-tuples reset the depth budget on each
recursion, so a hostile or pathological marshal stream of code-in-tuple-
in-code can blow the stack despite MAX_MARSHAL_STACK_DEPTH. Pass the
current depth through deserialize_code_inner and read_marshal_const_tuple
and decrement at each code-object/tuple boundary.

Also route dict keys through deserialize_value_after_header so TYPE_CODE
keys decode instead of failing with BadType.

* align compiler to CPython

* Align codegen with CPython compile.c

Rename CFG helpers and accessors to the names used in CPython's
compile.c (basicblock_next_instr, basicblock_last_instr,
basicblock_append_instructions, bb_has_fallthrough, is_jump,
make_cfg_traversal_stack, mark_warm/mark_cold, etc.). Drop the unused
boolop-folding gate, mark_cpython_cfg_label_block helper, and
ComprehensionLoopControl::iter_range field.

Track an is_coroutine flag on SymbolTable, set in async def, await, and
async comprehensions, and propagate it through non-generator
comprehensions per symtable_handle_comprehension().

Mark SetupCleanup/SetupFinally/SetupWith as has_arg pseudo-ops, mark
ForIter as a terminator, and add has_arg/has_const on AnyInstruction.
Fix Instruction::stack_effect_jump to delegate to the opcode's
stack_effect_jump rather than stack_effect.

* Align codegen IR with CPython CFG structures

* Match CPython CFG annotation offset arithmetic

* Propagate CPython CFG label translation errors

* Align CPython exception target labeling flow

* Propagate CPython CFG traversal stack allocation errors

* Match CPython optimize_load_fast allocation flow

* Propagate CPython basicblock allocation errors

* Propagate CPython redundant NOP cleanup errors

* Propagate CPython unused const cleanup errors

* Propagate CPython const folding errors

* Propagate CPython swaptimize allocation errors

* Match CPython list-to-tuple fold allocation

* Skip const folding on CPython allocation failures

* Skip subscript folding on CPython allocation failures

* Propagate CPython assembler allocation errors

* Propagate CPython localsplus allocation errors

* Propagate CPython localsplus setup allocation errors

* Propagate CPython jump label map allocation errors

* Propagate CPython instruction sequence allocation errors

* Propagate CPython instruction label allocation errors

* Guard CPython codegen block allocation

* Guard CPython label shadow allocation

* Propagate CPython label shadow allocation errors

* Align CPython c-array allocation updates

* Align CPython ref stack growth

* Match CPython CFG builder debug check

* Avoid Rust-only CFG append clone

* Reuse CPython cleared block slots

* Use CPython block append in copy_basicblock

* Match CPython cfg builder creation order

* Clear label map after CPython apply pass

* Match CPython cfg builder allocation check

* Propagate CPython c-array size errors

* Drop Rust-only label uniqueness check

* Drop Rust-only label shadow debug checks

* Propagate CFG block index overflow

* Propagate CFG label oparg overflow

* Model CPython basicblock instruction storage

* Drop Rust-only recorded CFG precheck

* Model CPython instruction sequence storage

* Propagate instruction sequence offset overflow

* Model CPython instruction sequence labels

* Match CPython jump offset arithmetic

* Match CPython exception table arithmetic

* Match CPython label index arithmetic

* Match CPython instruction offset casts

* Match CPython jump offset indexing

* Match CPython oparg locals casts

* Match CPython localsplus offset arithmetic

* Match CPython cell prefix indexing

* Match CPython C array growth arithmetic

* Match CPython label map allocation arithmetic

* Match CPython label map size tracking

* Use CPython label map size in sequence passes

* Assert CPython label map clearing invariants

* Match CPython label oparg assignment

* Match CPython compiler direct arithmetic

* Match CPython load fast local casts

* Match CPython load fast depth assert

* Match CPython resume depth flagging

* Match CPython stack depth arithmetic

* Drop Rust-only stack overflow error

* Return CPython stack depth directly

* Match CPython C array growth errors

* Match CPython instruction insert asserts

* Match CPython unreachable pseudo jump

* Match CPython CFG size guard

* Match CPython superinstruction assert

* Match CPython redundant jump assert

* Match CPython stackdepth errors

* Match CPython jump offset flow

* Match CPython assembler buffer defaults

* Match CPython bytecode emit growth

* Match CPython assembler entry growth

* Match CPython assembler growth overflow check

* Match CPython remove_unreachable structure

* Match CPython static swap flow

* Inline CPython code unit preprocessing

* Match CPython C array growth checks

* Match CPython label map size guard

* Match CPython load-fast flow

* Simplify CPython CFG condition flow

* Align exception fallthrough propagation

* Match CPython pseudo target table

* Match CPython annotations CFG assert

* Match CPython inverted op assert

* Match CPython many-locals guard

* Reject deopt opcodes in CFG stack effects

* Match CPython invalid stack effect error

* Test CPython deopt stack effect guard

* Match CPython load-fast extended-arg assert

* Match CPython instruction allocation asserts

* Match CPython basicblock last-instr asserts

* Match CPython opcode range asserts

* Assert CPython fallthrough line propagation invariant

* Assert CPython CFG target offset sign

* Assert CPython exception fallthrough invariant

* Assert CPython exception stack bounds

* Assert CPython traversal stack allocation

* Match CPython label-map allocation in shadow

* Mirror CPython label-map sentinel fill

* Match CPython CFG builder allocation asserts

* Match CPython exception stack structure

* Match CPython ref stack structure

* Match CPython CFG traversal stack structure

* Mirror CPython CFG traversal stack pointer

* Use CPython fixed exception handler stack

* Mirror CPython ref stack capacity field

* Match CPython swap optimizer scratch stack

* Align static swap helpers with CPython blocks

* Align swaptimize signature with CPython

* Match CPython redundant pair pass result

* Match CPython inline pass result

* Match CPython redundant NOP pass results

* Fix bytecode metadata after upstream rebase

* Match CPython opcode stack metadata

* Add CPython identifiers to cspell dictionary

Add CNOTAB, LNOTAB, ialloc, ioffset, iused, nblocks, ncellsused,
ncellvars, nextop, noffsets, nvars, swaptimize, untargeted to
.cspell.dict/cpython.txt for the new CFG/assembler code in
crates/codegen/src/ir.rs.

* Fix CI failures from bytecode-parity work

- clippy: drop redundant `test_` prefix on three test functions and
  remove an unnecessary `u32` cast in basicblock_clear_reuses_cpython_spare_slots_in_offset_order
- insta: regenerate nested_double_async_with snapshot to match the new
  CFG output that drops unreferenced labels after the redundant-NOP pass
- regrtest: drop `@expectedFailure` markers from test_func_args,
  test_meth_args (test_compile), test_disassemble_with,
  test_disassemble_try_finally (test_dis), and test_except_star
  (test_monitoring) which now pass

* Resync generated opcode metadata

Empty conf.toml since WithExceptStart and Setup{Cleanup,Finally,With}
stack effects already match CPython, so the TODO override entries are
stale and only cause CI hook diffs.

Regenerate opcode_metadata.rs and drop the matching SetupCleanup/
SetupFinally/SetupWith assertions on PseudoOpcode::has_arg(); their
`HAS_ARG` flag comes from pseudo definitions in bytecodes.c that the
upstream analyzer does not propagate through PseudoInstruction.properties,
so the generated has_arg() excludes them. has_target() still covers
these block-push pseudos via is_block_push().

* Drop is_block_push has_arg invariant

The CPython invariant `assert(OPCODE_HAS_ARG(op) || !IS_BLOCK_PUSH(op))`
relies on SETUP_{FINALLY,CLEANUP,WITH} carrying `HAS_ARG_FLAG` in
CPython's metadata. The autogen tool reads pseudo-opcode properties from
target instructions and does not propagate the pseudo's own
HAS_ARG flag, so PseudoOpcode::has_arg() omits these three opcodes.
Drop the debug_assert that fired inside py_freeze proc-macro expansion.

* Auto-generate has_eval_break and route AnyInstruction has_arg/has_const via macro

Add fn_has_eval_break to generate_rs_opcode_metadata.py using CPython's
Properties.eval_breaker, removing the hand-written matches! body for
Opcode::has_eval_break and PseudoOpcode::has_eval_break.

Forward has_arg/has_const from Instruction and PseudoInstruction to
their opcode, so AnyInstruction can use either_real_pseudo! like the
other has_* accessors instead of an open-coded match.
2026-05-28 09:19:11 +09:00
Bas Schoenmaeckers
ca412fce5d Add support for creating functions with the c-api (#7984) 2026-05-27 16:46:46 +09:00
Shahar Naveh
f95b7468f7 Autogen opcodes metadata (#7983) 2026-05-27 16:44:58 +09:00
Chanho Lee
1cb24c5ebb Reject non-ASCII digits in JSON numbers (#7982) 2026-05-27 16:40:52 +09:00
Jiseok CHOI
ce79cd4853 sqlite3: fix Blob.__setitem__ value range validation (#7981)
* sqlite3: fix Blob.__setitem__ value range validation

Previously, assigning an out-of-range integer (negative or > 255) or an
integer too large for i64 (e.g. 2**65) to a Blob index raised OverflowError
instead of ValueError.

Mirror CPython's ass_subscript_index logic:
- Convert the value via to_i64(), treating any overflow as -1
- Validate the result is in [0, 255], raising ValueError("byte must be in range(0, 256)") otherwise
- Separate deletion error messages: "item deletion" for index, "slice deletion" for slice

* sqlite3: fix Blob.__setitem__ negative-step slice write

In the step != 1 branch of Blob.ass_subscript, the loop used
  i_in_temp += step as usize
where step is isize. For negative steps (e.g. step = -2),
  (-2isize) as usize = 18446744073709551614
causing an out-of-bounds panic whenever slice_len >= 2.

Fix: use SaturatedSliceIter (already used by the read path) to iterate
over the correct absolute blob indices, then map each index back to a
temp buffer offset via abs_idx - range_start.

Also fix a Clippy lint: replace
  val < 0 || val > 255
with the idiomatic
  !(0..=255).contains(&val)

Add a regression test in extra_tests/snippets/stdlib_sqlite.py that
exercises blob[9:0:-2] (negative step, slice_len=5).

* fix: guard blob negative-step snippet from CPython 3.11 bug

* style: add blank line after import sys in stdlib_sqlite snippet (ruff)

* Update extra_tests/snippets/stdlib_sqlite.py

---------

Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com>
2026-05-27 16:40:05 +09:00
Bas Schoenmaeckers
dcb273ba68 Add capsule support to c-api (#7940) 2026-05-26 23:12:54 +09:00
Bas Schoenmaeckers
dd8d250ba3 Add list support to c-api (#7944)
* Add list support to c-api

* Review

* Fix Insert clamping
2026-05-26 23:12:01 +09:00
Shahar Naveh
3db172c025 Update ensurepip to 3.14.5 (#7860) 2026-05-26 23:11:43 +09:00
dependabot[bot]
fb531ecce3 Bump cranelift in the wasmtime group across 1 directory (#7980)
Bumps the wasmtime group with 1 update in the / directory: [cranelift](https://github.com/bytecodealliance/wasmtime).


Updates `cranelift` from 0.131.1 to 0.131.2
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)

---
updated-dependencies:
- dependency-name: cranelift
  dependency-version: 0.131.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: wasmtime
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-26 10:37:02 +09:00
Shahar Naveh
9701c46d86 Clippy rules (test related) (#7968) 2026-05-25 22:03:18 +09:00
James Clarke
021c78655e docs: expand AGENTS.md with test marker guidance and CI audit notes (#7963)
* docs: expand AGENTS.md with test marker guidance and CI audit notes

Document the four-way test marker decision (expectedFailure /
expectedFailureIf vs skip / skipIf, with skip reserved for tests that
crash the interpreter), add guidance on avoiding zizmor's
impostor-commit audit when pinning third-party actions, link the wiki
guide for syncing tests from upstream CPython, and note the grep recipe
for finding WIP TODO: RUSTPYTHON entries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: add pre-commit checks rule, trim CI section per review

Add a "CRITICAL: Pre-commit Checks" subsection requiring prek and the
full test suite to pass before any commit. Shrink the CI Workflows
section to a single sentence per @ShaharNaveh's review feedback:
contributors only need to know that workflow changes must pass a zizmor
scan in CI, not the full impostor-commit explainer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: warn against doc comments on pyattr/pyclass/pyfunction items

- Note that rustpython-doc supplies CPython docstrings via the derive macros
- Flag that `///` comments override that source and are dropped on `#[pyattr]`

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:02:22 +09:00
dependabot[bot]
411230a46c Bump j178/prek-action from 2.0.3 to 2.0.4 (#7973)
Bumps [j178/prek-action](https://github.com/j178/prek-action) from 2.0.3 to 2.0.4.
- [Release notes](https://github.com/j178/prek-action/releases)
- [Commits](6ad8027733...bdca6f102f)

---
updated-dependencies:
- dependency-name: j178/prek-action
  dependency-version: 2.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-25 21:20:30 +09:00
Shahar Naveh
b69644196a Upgrade pkcs8 to 0.11 (#7977) 2026-05-25 21:20:13 +09:00
dependabot[bot]
a6fee92683 Bump the wasmtime group across 1 directory with 2 updates (#7975)
Bumps the wasmtime group with 1 update in the / directory: [cranelift-jit](https://github.com/bytecodealliance/wasmtime).


Updates `cranelift-jit` from 0.131.1 to 0.131.2
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)

Updates `cranelift-module` from 0.131.1 to 0.131.2
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)

---
updated-dependencies:
- dependency-name: cranelift-jit
  dependency-version: 0.131.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: wasmtime
- dependency-name: cranelift-module
  dependency-version: 0.131.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: wasmtime
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-25 18:40:48 +09:00
dependabot[bot]
f4696ea890 Bump peaceiris/actions-gh-pages from 4.0.0 to 4.1.0 (#7972)
Bumps [peaceiris/actions-gh-pages](https://github.com/peaceiris/actions-gh-pages) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/peaceiris/actions-gh-pages/releases)
- [Changelog](https://github.com/peaceiris/actions-gh-pages/blob/main/CHANGELOG.md)
- [Commits](4f9cc6602d...84c30a85c1)

---
updated-dependencies:
- dependency-name: peaceiris/actions-gh-pages
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-25 18:40:33 +09:00
dependabot[bot]
0237a1d520 Bump taiki-e/install-action from 2.77.6 to 2.79.1 (#7971)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.77.6 to 2.79.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](c070f87102...b550161ef8)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.79.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-25 18:40:17 +09:00
dependabot[bot]
6bbe24a725 Bump zizmorcore/zizmor-action from 0.5.3 to 0.5.6 (#7970)
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.5.3 to 0.5.6.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](b1d7e1fb5d...5f14fd08f7)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.5.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-25 18:40:02 +09:00
dependabot[bot]
f9e4988cf5 Bump github/gh-aw from 0.73.0 to 0.74.4 (#7969)
Bumps [github/gh-aw](https://github.com/github/gh-aw) from 0.73.0 to 0.74.4.
- [Release notes](https://github.com/github/gh-aw/releases)
- [Changelog](https://github.com/github/gh-aw/blob/main/CHANGELOG.md)
- [Commits](4d44d0e898...2c1a237d20)

---
updated-dependencies:
- dependency-name: github/gh-aw
  dependency-version: 0.74.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-25 18:39:43 +09:00
James Clarke
a5775e0c07 Fix thread teardown panic when weakref callback fires during cleanup (#7965) 2026-05-25 13:55:32 +09:00
fanninpm
bc3d00e879 Replace ahash with rapidhash (#7954)
* Add `rapidhash` to list of dependencies

* Use `rapidhash::quality::RandomState` in `codegen` crate

* Use `rapidhash::quality::RandomState` in `stdlib` crate

* Use `rapidhash::quality::RandomState` in `vm` crate

* Remove `ahash` from lists of dependencies
2026-05-25 13:53:54 +09:00
Shahar Naveh
52305c0c72 Skip flaky test (#7967) 2026-05-24 20:00:32 +09:00
Shahar Naveh
7011942e4e Add builtin.PythonFinalizationError (#7966)
* Add PythonFinalizationError to builtins

* Patch failing tests (unrelated)

* Unmark passing test

* Update `exception_hierarchy.txt` to 3.14.5
2026-05-24 19:58:16 +09:00
dependabot[bot]
438925401f Bump qs and express in /wasm/demo (#7959)
Bumps [qs](https://github.com/ljharb/qs) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `qs` from 6.14.2 to 6.15.2
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.2...v6.15.2)

Updates `express` from 4.22.1 to 4.22.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/v4.22.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/v4.22.1...v4.22.2)

---
updated-dependencies:
- dependency-name: express
  dependency-version: 4.22.2
  dependency-type: indirect
- dependency-name: qs
  dependency-version: 6.15.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-24 19:57:20 +09:00
Shahar Naveh
2fabf38d8f Impl sys.audithook (#7960)
* Update tests

* Add basic audit support

* Add audit for `time.sleep`

* Add some for `socket`

* Some syslog

* Some sys related audits

* some marshal

* monitoring callback

* Mark failing tests

* clippy

* Clippy

* clippy

* mark failing test

* mark more

* Update `test_sys_setprofile.py` to 3.14.5

* Mark failing tests
2026-05-24 19:56:35 +09:00
Shahar Naveh
d7d936575c General code nitpicks (#7955) 2026-05-24 19:55:22 +09:00
Jeong, YunWon
b5ff41c219 Align marshal and .pyc with CPython 3.14 (#7958)
* Share marshal ref table between code object and its internals

read_marshal_bytes, _str, _str_vec, _name_tuple, and _const_tuple now
take a shared ref table and resolve TYPE_REF / register FLAG_REF
entries. deserialize_code is split into a public wrapper and an inner
function that receives the ref table; deserialize_value_depth opens a
fresh inner ref space when it hits Type::Code, mirroring CPython's
behaviour of putting the code object itself at ref slot 0. Nested code
objects inside const tuples reuse the surrounding code's ref space via
the new read_const_value helper.

* Align PYC magic number, FORMAT_VERSION, and header check with CPython 3.14

PYC_MAGIC_NUMBER changes from 2994 to 3627, matching CPython 3.14's
pyc_magic_number_token (0x0a0d0e2b). marshal FORMAT_VERSION drops from
5 to 4 (the encoder/marshal.version value; the decoder already accepts
both). check_pyc_magic_number_bytes now compares all four magic bytes
instead of the first two.

* Add CPython 3.14 .pyc decoding regression tests

Two fixture-based tests pin the marshal decoder against actual CPython
3.14 marshal.dumps() output: a trivial module that exercises FLAG_REF
plus TYPE_REF for qualname, and a module with a nested function that
exercises ref sharing between a const tuple and its surrounding code
object.

* Accept CPython-tagged .pyc as read-only bytecode source

SourceFileLoader.get_code now also looks for .pyc files using
_RP_FALLBACK_CACHE_TAGS (currently ('cpython-314',)) in addition to
sys.implementation.cache_tag. The matched .pyc is only used for
reading; recompilation still writes to the RustPython-tagged path, so
CPython's .pyc is never overwritten. Source-stat / hash / timestamp
validation logic is unchanged.

* Apply rustfmt to marshal helpers

* Marshal PySlice from format version 4 instead of 5

CPython's marshal supports TYPE_SLICE from format version 4 onwards
and that is the default version. Rejecting slice dumps below version
5 made marshal.dumps(slice(...)) fail with the default version and
broke test.test_marshal.SliceTestCase.test_slice.

* Revert "Accept CPython-tagged .pyc as read-only bytecode source"

Lib/importlib/_bootstrap_external.py is CPython's own code copied
verbatim; local patches here defeat compatibility tracking. The
cpython-XX cache_tag fallback needs to live on the RustPython side
(Rust code or sys.implementation.cache_tag policy), not as edits to
the imported standard library.

This reverts commit 1fc426d0fb5fcdb50d35cad13bbb43e8f6ce1c7f.

* Format sys.implementation.cache_tag as cpython-{MAJOR}{MINOR}

Use the CPython compatibility version (e.g. cpython-314) instead of
the rustpython-{MAJOR_IMPL}_{MINOR_IMPL} interpreter version string.

* Set marshal FORMAT_VERSION to 5 to match CPython 3.14.5

Py_MARSHAL_VERSION is 5 in CPython 3.14.5 (Include/marshal.h:16) and
TYPE_SLICE serialization rejects version < 5 (Python/marshal.c:720).
Restore the same threshold and constant so marshal.version and the
slice-marshal gate match CPython.

* Thread marshal recursion depth through nested code objects

Code objects embedded in const-tuples reset the depth budget on each
recursion, so a hostile or pathological marshal stream of code-in-tuple-
in-code can blow the stack despite MAX_MARSHAL_STACK_DEPTH. Pass the
current depth through deserialize_code_inner and read_marshal_const_tuple
and decrement at each code-object/tuple boundary.

Also route dict keys through deserialize_value_after_header so TYPE_CODE
keys decode instead of failing with BadType.
2026-05-24 19:30:42 +09:00
Shahar Naveh
e1d9a1123e Skip flaky tests (#7961) 2026-05-24 13:48:00 +09:00
Ivan Mironov
b9efe10537 Add missing test for select.select() (#7953)
This is a follow up for https://github.com/RustPython/RustPython/pull/7948
2026-05-24 13:43:56 +09:00
Jeong, YunWon
d3272e752b Align codegen metadata with CPython (#7952) 2026-05-23 20:16:03 +09:00
Shahar Naveh
f3b83efcee Update test_structseq.py to 3.14.5 (#7951) 2026-05-22 20:11:57 +09:00
Shahar Naveh
1a013930a7 Update test_timeout.py to 3.14.5 (#7950) 2026-05-22 20:11:27 +09:00
Shahar Naveh
c513b923df Update test_socketserver.py to 3.14.5 (#7949) 2026-05-22 20:10:49 +09:00
Ivan Mironov
cf3b6397b2 Fix panic in select.select() when too many FDs specified (#7948)
Also:

* Add regression test into existing `extra_tests/snippets/stdlib_select.py`
* Stop calculating nfds on Windows as it is ignored there

Panic:

	thread 'main' (189598) panicked at /root/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/libc-0.2.186/src/unix/linux_like/mod.rs:1777:9:
	index out of bounds: the len is 16 but the index is 16
	note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

	thread 'main' (189598) panicked at library/core/src/panicking.rs:225:5:
	panic in a function that cannot unwind
	stack backtrace:
	   0:     0xaaab763a0b88 - <<std[1934960bf7f41d0a]::sys::backtrace::BacktraceLock>::print::DisplayBacktrace as core[f1abae5f1257fe69]::fmt::Display>::fmt
	   1:     0xaaab75590ff0 - core[f1abae5f1257fe69]::fmt::write
	   2:     0xaaab763a94fc - <std[1934960bf7f41d0a]::sys::stdio::unix::Stderr as std[1934960bf7f41d0a]::io::Write>::write_fmt
	   3:     0xaaab7638b714 - std[1934960bf7f41d0a]::panicking::default_hook::{closure#0}
	   4:     0xaaab7639b288 - std[1934960bf7f41d0a]::panicking::default_hook
	   5:     0xaaab7639b478 - std[1934960bf7f41d0a]::panicking::panic_with_hook
	   6:     0xaaab7638b7ec - std[1934960bf7f41d0a]::panicking::panic_handler::{closure#0}
	   7:     0xaaab76382654 - std[1934960bf7f41d0a]::sys::backtrace::__rust_end_short_backtrace::<std[1934960bf7f41d0a]::panicking::panic_handler::{closure#0}, !>
	   8:     0xaaab7638c504 - __rustc[b7425922bef61dcf]::rust_begin_unwind
	   9:     0xaaab754f778c - core[f1abae5f1257fe69]::panicking::panic_nounwind_fmt
	  10:     0xaaab754f7714 - core[f1abae5f1257fe69]::panicking::panic_nounwind
	  11:     0xaaab754f786c - core[f1abae5f1257fe69]::panicking::panic_cannot_unwind
	  12:     0xaaab75a6283c - rustpython_vm::function::builtin::<impl rustpython_vm::function::builtin::sealed::PyNativeFnInternal<(rustpython_vm::function::builtin::OwnedParam<T1>,rustpython_vm::function::builtin::OwnedParam<T2>,rustpython_vm::function::builtin::OwnedParam<T3>,rustpython_vm::function::builtin::OwnedParam<T4>),R,rustpython_vm::vm::VirtualMachine> for F>::call_::h2471c8e242c9b51d
	  13:     0xaaab75db1e68 - rustpython_vm::types::slot::Callable::slot_call::hd1c1ad0ad14f306b
	  14:     0xaaab762c0a50 - rustpython_vm::protocol::callable::PyCallable::invoke::h9f6d571fca351ca6
	  15:     0xaaab75c550e8 - rustpython_vm::protocol::callable::<impl rustpython_vm::object::core::PyObject>::call_with_args::hed1f4a61aba2dced
	  16:     0xaaab762e7c24 - rustpython_vm::frame::ExecutingFrame::execute_call::h0ad3490dd74ed1e3
	  17:     0xaaab762fed40 - rustpython_vm::frame::ExecutingFrame::run::hcf90f0950fc26812
	  18:     0xaaab761e6768 - rustpython_vm::vm::VirtualMachine::with_frame::hd49ba6fcdf2422e2
	  19:     0xaaab75c45398 - rustpython_vm::builtins::function::<impl rustpython_vm::object::core::Py<rustpython_vm::builtins::function::PyFunction>>::invoke_with_locals::h42de3d2316941ce2
	  20:     0xaaab76132a80 - rustpython_vm::builtins::function::vectorcall_function::h7331cb67b334e867
	  21:     0xaaab763369d8 - rustpython_vm::protocol::callable::<impl rustpython_vm::object::core::PyObject>::vectorcall::h9019c5d16685c89a
	  22:     0xaaab762f4b54 - rustpython_vm::frame::ExecutingFrame::execute_call_vectorcall::h120134e11a58c946
	  23:     0xaaab76302a7c - rustpython_vm::frame::ExecutingFrame::run::hcf90f0950fc26812
	  24:     0xaaab761e6768 - rustpython_vm::vm::VirtualMachine::with_frame::hd49ba6fcdf2422e2
	  25:     0xaaab761e7f24 - rustpython_vm::vm::VirtualMachine::run_code_obj::h354618be6e5cc553
	  26:     0xaaab761e2d18 - rustpython_vm::vm::python_run::file_run::<impl rustpython_vm::vm::VirtualMachine>::run_any_file::h783d3127fbc0b523
	  27:     0xaaab757d700c - rustpython::run_rustpython::h354efb8d817cefbf
	  28:     0xaaab757c79e0 - std::thread::local::LocalKey<T>::with::hc9728e249843a926
	  29:     0xaaab757db860 - rustpython_vm::vm::interpreter::Interpreter::run::h42ac1fe9ed2287a2
	  30:     0xaaab757d7b30 - rustpython::run::hf14a209db5b4289c
	  31:     0xaaab757e2eb4 - rustpython::main::h1b59d8e13276ac48
	  32:     0xaaab757e2eec - std::sys::backtrace::__rust_begin_short_backtrace::h47e4b1f073f2155c
	  33:     0xaaab757e2ed4 - std::rt::lang_start::{{closure}}::h663a6c3dc7d80101
	  34:     0xaaab76399fd4 - std[1934960bf7f41d0a]::rt::lang_start_internal
	  35:     0xaaab757e2f44 - main
	  36:     0xfffed057655c - __libc_start_call_main
	  37:     0xfffed057663c - __libc_start_main@@GLIBC_2.34
	  38:     0xaaab755526f0 - _start
	  39:                0x0 - <unknown>
	thread caused non-unwinding panic. aborting.
	Aborted                    (core dumped) cargo run --release -- extra_tests/snippets/stdlib_select.py
2026-05-22 20:10:24 +09:00
Ivan Mironov
2a163609cd Rustls integration improvements (#7946)
* Do not call `import socket` on each send()/recv() when using rustls

Use method references cached during socket creation.

* Implement reading of at most one TLS record from socket

Previous algorithm didn't take into account that recv() may return less
data than requested even for blocking sockets.

* Remove special handling of rustls "buffer full" errors

First of all, existing code does not really work and this leads to an
infinite loop: https://github.com/RustPython/RustPython/issues/7891

Second, this should never happen when rustls used properly (wrt
wants_read() and wants_write()) and thus all such errors are
implementation bugs that must be properly fixed.

* Replace own TlsConnection with rustls::Connection

* Fix waiting on a socket

1) Ensure that socket_wait() called from TLS glue code allows threads
2) Ensure that socket_wait() called from TLS glue code properly handles
   EINTR on *nix
3) Ensure that select() or poll() error conditions are checked
4) Use poll() on *nix so socket descriptor values are not limited

* Remove dead code from rustls glue

* Do not present rustls errors as OSError(0, "Success")

* Remove infinite loop "detection" from rustls glue

TLS handshake cannot be infinite. Any infinite loop here is a serious
bug in implementation and should be fixed properly.

This code triggers in some cases (very short reads) with misleading
`ssl_error.SSLWantReadError: The operation did not complete (read)`.

* Add test for 1-byte max recv in TLS client

* Add regression test for https://github.com/RustPython/RustPython/issues/7891

* Fix constants in rustls glue code

* Deduplicate verify flags / record-size constants
* Larger "max encrypted TLS record length"
2026-05-22 20:04:15 +09:00
Bas Schoenmaeckers
4eb9534646 Add complex number support to c-api (#7945) 2026-05-22 20:00:46 +09:00
Bas Schoenmaeckers
ab5bc43359 Add float support to c-api (#7943) 2026-05-22 20:00:16 +09:00
Jeong, YunWon
06f73f2ae1 Align nested code object bytecode parity (#7942)
* Align nested code object bytecode parity

* Align CPython jump-back parity

* Align match guard block parity with CPython

* Align CPython block borrow parity

* Align CPython load-fast barrier parity

* Align CPython try-end label parity

* Align CPython try-except end label barriers

* Align more CPython bytecode edge cases

* Align bare except finally bytecode layout
2026-05-22 03:53:35 +09:00
Bas Schoenmaeckers
c845861c4f Add bool support to c-api (#7938) 2026-05-21 09:59:40 +09:00
Bas Schoenmaeckers
a136f9047b Add basic dict function to c-api (#7929)
* Add basic dict function to c-api

* Fix iter

* Do not use mapping protocol
2026-05-21 09:59:09 +09:00
Jeong, YunWon
948924a14b Align cleanup bytecode layout with CPython (#7935)
* Align cleanup bytecode layout with CPython

* Align bytecode anchors with CPython

* Fix bytecode CFG review regressions
2026-05-21 08:11:00 +09:00
Bas Schoenmaeckers
ae6c16093e Add call functions to c-api (#7937)
* Add call functions to c-api

* Review

* Move conversion impl to c-api
2026-05-21 00:22:55 +09:00
dependabot[bot]
a6f13b98fb Bump openssl from 0.10.79 to 0.10.80 (#7931)
Bumps [openssl](https://github.com/rust-openssl/rust-openssl) from 0.10.79 to 0.10.80.
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-v0.10.79...openssl-v0.10.80)

---
updated-dependencies:
- dependency-name: openssl
  dependency-version: 0.10.80
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-20 23:10:08 +09:00
Shahar Naveh
4daa526dc1 Update tzinfo to 3.14.5 (#7934)
* Update `tzinfo` to 3.14.5

* Mark failing tests
2026-05-20 23:09:50 +09:00
Shahar Naveh
d073964aaa Remove unused binaries (#7936) 2026-05-20 22:21:04 +09:00
Shahar Naveh
ee006af13e Align more error messages with CPython 3.14.5 (#7933)
* Align more error messages with CPython 3.14.5

* Align patches for `test_eof.py`

* Unmark passing test

* Fix more
2026-05-20 22:20:13 +09:00
Bas Schoenmaeckers
de06fc0923 Add eval function to c-api (#7927)
* Add eval function to c-api

* Extract `Py_CompileString` start values into constants
2026-05-20 22:18:06 +09:00
Jeong, YunWon
ae3804fb21 more hostenv isolation (#7886)
* Convert host_env Windows path/argv params from raw *const u16 to &WideCStr

* Migrate remaining winapi raw u16 pointer signatures to typed references

* Migrate winreg pub unsafe fn string parameters to typed references

* Add ToPyException impls for host_env error types (PyPy wrap_oserror analog)

* Add CheckLibcResult helper and apply to socket/fcntl/shm/posix_wasi

* Add Win32 BOOL/HANDLE check helpers; apply check helpers across host_env

* Apply Win32/libc check helpers to overlapped/testconsole/os.rs

* Apply Win32 check helpers to winapi.rs (partial)

* Apply Win32 check helpers across more winapi.rs functions

* Apply Win32 check helpers to nt.rs (partial)

* Add CheckWin32Sentinel helper; apply to nt.rs INVALID_HANDLE_VALUE/INVALID_FILE_ATTRIBUTES patterns

* Add OwnedHandle / HandleToOwned helper; apply to mmap create_named_mapping leak path

* Use OwnedHandle RAII in nt::pipe to eliminate manual cleanup on error path

* Use OwnedHandle in nt::chmod_follow; hoist HandleToOwned import

* Drop rustix dependency from vm crate

Remove unused IntoPyException impl for rustix::io::Errno and the
rustix entry in crates/vm/Cargo.toml. rustix is now only depended on
by host_env.

* Fix CI failures: cross-platform regressions

- winapi.rs: pass None to create_event_w; the recent Option<&WideCStr>
  migration left one call site still passing a raw null pointer.
- exceptions.rs: gate ToPyException for LockfError with
  cfg(any(unix, target_os = "wasi")), matching host_env::fcntl's own
  cfg. The previous cfg let it compile on wasm32-unknown-unknown where
  host_env::fcntl does not exist.
- io_unsupported.rs: derive Eq on FileMode alongside PartialEq to
  satisfy clippy::derive_partial_eq_without_eq.

* Fix CI failures: cfg gates and unused imports

- exceptions.rs: gate ToPyException for LockfError with
  cfg(all(unix, not(target_os = "redox"))) to match the type's own
  cfg in host_env/src/fcntl.rs (LockfError is not built on wasi).
- signal.rs: CheckLibcResult is only used in unix-gated functions;
  split import so it is not pulled in for windows.
- mmap.rs: remove CheckWin32Handle from imports; no longer used after
  switching to HandleToOwned-based RAII.
- overlapped.rs: remove INVALID_HANDLE_VALUE from connect_pipe import;
  the call now uses .check_valid().

* Fix CI failures: rustfmt and windows unused import

- signal.rs: reorder cfg-gated imports per rustfmt.
- socket.rs: gate ToPyException import to cfg(all(unix, not(target_os = "redox")));
  it is only used inside sendmsg which has the same gate, so it was unused
  on windows.

* Push remaining libc/extern callsites from vm into host_env

Add host_env wrappers and replace the corresponding vm call sites:
- host_env::errno::strerror_string for libc::strerror
- host_env::io::write_stderr_raw for libc::write(STDERR_FILENO,...)
- host_env::locale::localeconv_data reused from vm::format
- host_env::os::abort for the inline abort extern
- host_env::os::urandom wraps getrandom; getrandom moves from vm to host_env
- host_env::posix::lchmod for the macOS/BSD lchmod extern
- host_env::posix::fcopyfile for the macOS fcopyfile extern
- host_env::nt::wputenv for the Windows _wputenv extern

vm/format.rs's get_locale_info now uses host_env on both unix and windows
instead of the unix-only libc::localeconv path.

* Move time tz state and winsound FFI into host_env

- host_env::time::tz: wraps the libc tzset/timezone/daylight/tzname
  globals on non-msvc, non-wasm32 targets. vm::stdlib::time now reads
  these via the typed wrappers instead of declaring its own externs.
- host_env::winsound (windows): exposes PlaySoundW (via a typed
  PlaySoundSource enum), Beep, and MessageBeep. vm::stdlib::winsound
  drops its inline FFI block and routes through host_env.

* Migrate unsetenv to host_env::nt::wputenv; rustfmt

- vm::stdlib::os::unsetenv had a second _wputenv call site that still
  referenced the removed inline extern. Route it through
  host_env::nt::wputenv like putenv.
- rustfmt fixups in exceptions.rs (boolean chain layout) and the two
  winsound files.

* Address PR review comments

- host_env::winapi::create_process: assert that the command_line buffer
  is NUL-terminated and that the env block ends with a double-NUL,
  matching the Win32 CreateProcessW contract.
- stdlib::overlapped CreateEvent: replace WideCString::from_str_truncate
  with the fallible from_str(), so embedded NULs in the event name
  surface as ValueError instead of being silently truncated.
- vm::exceptions::ReadlinkError::NotSymbolicLink now maps to OSError
  (matches Win32 ERROR_NOT_A_REPARSE_POINT semantics) rather than
  ValueError.
- winreg::ConnectRegistry: route the non-zero return through the
  existing os_error_from_windows_code helper so the resulting exception
  carries the real winerror/message instead of a generic OSError.

* Fix CI failures and address review follow-ups

CI failures:
- rustfmt cleanup in exceptions.rs after the ReadlinkError change.
- vm/stdlib/os.rs: drop unused ToWideString import that the wputenv
  migration left behind.
- vm/stdlib/winsound.rs: replace explicit `&*buf` with `&buf` to
  satisfy clippy::explicit_auto_deref.
- Lib/test/test_format.py, Lib/test/test_types.py: drop the now-stale
  expectedFailureIfWindows decorators on the locale-format tests; the
  Windows path now reads real `localeconv` data via host_env so these
  tests pass.

Review follow-ups:
- host_env::winapi::create_process: switch the new buffer terminator
  checks from `assert!` to fallible validators returning
  `io::ErrorKind::InvalidInput`, so bad inputs stay recoverable at
  the API boundary.
- host_env::winsound::play_sound: reject `Memory(_)` together with
  `SND_ASYNC` (lifetime-unsafe) and `SND_MEMORY` without a
  `Memory(_)` source. Expand `PlaySoundError` into a variant enum.
- vm::stdlib::_winapi::CreateProcess: route the Win32 path/argv strings
  through `as_wtf8().to_wide_cstring()` like the rest of the Windows
  API surface; `expect_str()` could panic on Python strings containing
  lone surrogates.
2026-05-20 16:16:14 +09:00
Jeong, YunWon
d51069bc7f Align load-fast borrow barriers with CPython (#7930)
* Align load-fast borrow barriers with CPython

* Allow CPython ifexp compiler name in spellcheck

* Address load-fast passthrough review feedback

* Traverse empty load-fast passthrough predecessors

* Extract load-fast test helpers
2026-05-20 14:11:36 +09:00
Shahar Naveh
0063a6d18b Align more error messages with CPython 3.14.5 (#7928) 2026-05-20 08:59:59 +09:00
Bas Schoenmaeckers
cea21f953d Add more c-api error functions (#7915)
* Add more c-api error functions

* Return NULL when no cause is set
2026-05-20 08:58:45 +09:00
Jeong, YunWon
62b081b893 Resolve test_inspect bytecode parity gaps (#7926)
* Resolve test_inspect bytecode parity gaps

* Address code review feedback
2026-05-19 22:47:58 +09:00
Bas Schoenmaeckers
26f5bbf077 Add type name c-api functions (#7925) 2026-05-19 21:03:39 +09:00
dependabot[bot]
ccf1508a06 Bump webpack-dev-server from 5.2.1 to 5.2.4 in /wasm/demo (#7924)
Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 5.2.1 to 5.2.4.
- [Release notes](https://github.com/webpack/webpack-dev-server/releases)
- [Changelog](https://github.com/webpack/webpack-dev-server/blob/main/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-server/compare/v5.2.1...v5.2.4)

---
updated-dependencies:
- dependency-name: webpack-dev-server
  dependency-version: 5.2.4
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-19 21:03:14 +09:00
Joshua Megnauth
c5143aa82f Const eval all of version.rs (#7923)
`version.rs` essentially consists of constants that can be baked in at
compile time. I moved most of `version.rs` to `build.rs`. The constants
are passed via rustc's environment then stored in the binary.
2026-05-19 21:02:56 +09:00
Shahar Naveh
0a2461a704 Update test_grammar.py to 3.14.5 (#7913)
* Update `test_grammar.py` to 3.14.5

* Update `test_syntax.py` to 3.14.5
2026-05-19 20:59:00 +09:00
Bas Schoenmaeckers
276a0e6c42 Add tuple support to c-api (#7907) 2026-05-19 20:57:06 +09:00
Jeong, YunWon
d8dee81157 Bytecode parity (#7885)
* Align codegen CFG cleanup with CPython

* Align codegen bytecode with CPython 3.14

* Remove remove_jump_target_line_nops

The pass dropped a same-line NOP at the start of any jump-targeted block
whose body advanced to a later line, but CPython's basicblock_remove_redundant_nops
only consults the previous/next instruction inside the same block. The
extra pass deleted the else-body line trace anchor exercised by
test_nested_double_async_with.

Ignore test_conditional_break_finally_does_not_keep_break_cleanup_nop;
the break NOP lands in a separate block from the inlined finally body,
so same-line successor elision does not apply here.

* Keep except_handler blocks reachable in eliminate_unreachable_blocks

After convert_pseudo_ops lowers SETUP_FINALLY to a plain NOP, the only
remaining link from the try body to the except_handler block was the
per-instruction except_handler annotation. When earlier passes had
already removed every NOP that carried that annotation (e.g. an empty
try body with `pass`), the handler block became unreachable from the
entry block and its instructions were cleared, dropping the handler
entirely. Seed reachability with blocks already marked except_handler
so handler dispatch survives independent of the in-block annotation.

Also drop two expectedFailure markers in test_patma whose match-tracing
expectations now pass.

* Align bytecode CFG cleanup with CPython
2026-05-19 19:24:45 +09:00
Shahar Naveh
e8d7437d91 Update test_types.py to 3.14.5 (#7912) 2026-05-19 08:21:19 +00:00
dependabot[bot]
53941295a2 Bump taiki-e/install-action from 2.76.0 to 2.77.6 (#7919)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.76.0 to 2.77.6.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](711e1c3275...c070f87102)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.77.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-19 13:36:00 +09:00
dependabot[bot]
1fe4485c10 Bump siphasher from 1.0.2 to 1.0.3 (#7922)
Bumps [siphasher](https://github.com/jedisct1/rust-siphash) from 1.0.2 to 1.0.3.
- [Commits](https://github.com/jedisct1/rust-siphash/compare/1.0.2...1.0.3)

---
updated-dependencies:
- dependency-name: siphasher
  dependency-version: 1.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-19 13:34:28 +09:00
dependabot[bot]
50f235aded Bump nix from 0.31.2 to 0.31.3 in the unix group across 1 directory (#7921)
Bumps the unix group with 1 update in the / directory: [nix](https://github.com/nix-rust/nix).


Updates `nix` from 0.31.2 to 0.31.3
- [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/compare/v0.31.2...v0.31.3)

---
updated-dependencies:
- dependency-name: nix
  dependency-version: 0.31.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: unix
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-19 13:34:16 +09:00
dependabot[bot]
b902ffdcf8 Bump github/gh-aw from 0.71.3 to 0.73.0 (#7918)
Bumps [github/gh-aw](https://github.com/github/gh-aw) from 0.71.3 to 0.73.0.
- [Release notes](https://github.com/github/gh-aw/releases)
- [Changelog](https://github.com/github/gh-aw/blob/main/CHANGELOG.md)
- [Commits](2f2a6f572b...4d44d0e898)

---
updated-dependencies:
- dependency-name: github/gh-aw
  dependency-version: 0.73.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-19 13:34:06 +09:00
dependabot[bot]
9c0557820b Bump cargo-bins/cargo-binstall from 1.19.0 to 1.19.1 (#7917)
Bumps [cargo-bins/cargo-binstall](https://github.com/cargo-bins/cargo-binstall) from 1.19.0 to 1.19.1.
- [Release notes](https://github.com/cargo-bins/cargo-binstall/releases)
- [Changelog](https://github.com/cargo-bins/cargo-binstall/blob/main/release-plz.toml)
- [Commits](4852a15cf0...aaa84a43ae)

---
updated-dependencies:
- dependency-name: cargo-bins/cargo-binstall
  dependency-version: 1.19.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-19 13:33:57 +09:00
Ivan Mironov
150b9103a8 Fix "tried to pop from empty stack" with BlockExpr and const (#7916)
* Add tests for Mode::BlockExpr and VirtualMachine::run_block_expr

test_block_expr_return_const() panics currently.

* Fix "tried to pop from empty stack" with BlockExpr and const

Constant expressions without side effects are optimized away unless in
an interactive mode[1]. This causes panic when last statement in a block
is a constant and Mode::BlockExpr is used as this constant value must be
returned to caller.

Example panic:

    ---- vm::python_run::tests::test_block_expr_return_const stdout ----
    [crates/vm/src/frame.rs:9653:9] self = ExecutingFrame {
        code: code: <code object <module> at ??? file "<embedded>", line 1>,
        stack_len: 0,
    }

    thread 'vm::python_run::tests::test_block_expr_return_const' (2640752) panicked at crates/vm/src/frame.rs:9410:18:
    tried to pop from empty stack
    stack backtrace:
       0: __rustc::rust_begin_unwind
                 at /rustc/59807616e1fa2540724bfbac14d7976d7e4a3860/library/std/src/panicking.rs:689:5
       1: core::panicking::panic_fmt
                 at /rustc/59807616e1fa2540724bfbac14d7976d7e4a3860/library/core/src/panicking.rs:80:14
       2: rustpython_vm::frame::ExecutingFrame::fatal
                 at ./src/frame.rs:9654:9
       3: rustpython_vm::frame::ExecutingFrame::pop_stackref_opt
                 at ./src/frame.rs:9410:18
       4: rustpython_vm::frame::ExecutingFrame::pop_stackref
                 at ./src/frame.rs:9420:18
       5: rustpython_vm::frame::ExecutingFrame::pop_value
                 at ./src/frame.rs:9435:14
       6: rustpython_vm::frame::ExecutingFrame::execute_instruction
                 at ./src/frame.rs:3391:34
       7: rustpython_vm::frame::ExecutingFrame::run
                 at ./src/frame.rs:1623:31
       8: rustpython_vm::frame::<impl rustpython_vm::object::core::Py<rustpython_vm::frame::Frame>>::run::{{closure}}
                 at ./src/frame.rs:1098:44
       9: rustpython_vm::frame::<impl rustpython_vm::object::core::Py<rustpython_vm::frame::Frame>>::with_exec
                 at ./src/frame.rs:1093:9
      10: rustpython_vm::frame::<impl rustpython_vm::object::core::Py<rustpython_vm::frame::Frame>>::run
                 at ./src/frame.rs:1098:14
      11: rustpython_vm::vm::VirtualMachine::run_frame::{{closure}}
                 at ./src/vm/mod.rs:1201:44
      12: rustpython_vm::vm::VirtualMachine::with_frame_impl::{{closure}}::{{closure}}
                 at ./src/vm/mod.rs:1610:60
      13: rustpython_vm::vm::VirtualMachine::dispatch_traced_frame
                 at ./src/vm/mod.rs:1689:22
      14: rustpython_vm::vm::VirtualMachine::with_frame_impl::{{closure}}
                 at ./src/vm/mod.rs:1610:22
      15: rustpython_vm::vm::VirtualMachine::with_recursion
                 at ./src/vm/mod.rs:1547:9
      16: rustpython_vm::vm::VirtualMachine::with_frame_impl
                 at ./src/vm/mod.rs:1572:14
      17: rustpython_vm::vm::VirtualMachine::with_frame
                 at ./src/vm/mod.rs:1555:14
      18: rustpython_vm::vm::VirtualMachine::run_frame
                 at ./src/vm/mod.rs:1201:20
      19: rustpython_vm::vm::VirtualMachine::run_code_obj
                 at ./src/vm/mod.rs:1120:14
      20: rustpython_vm::vm::python_run::<impl rustpython_vm::vm::VirtualMachine>::run_block_expr
                 at ./src/vm/python_run.rs:51:14
      21: rustpython_vm::vm::python_run::tests::test_block_expr_return_const::{{closure}}
                 at ./src/vm/python_run.rs:190:47
      22: rustpython_vm::vm::interpreter::Interpreter::enter::{{closure}}
                 at ./src/vm/interpreter.rs:366:39
      23: rustpython_vm::vm::thread::set_current_vm::{{closure}}
                 at ./src/vm/thread.rs:107:9
      24: std::thread::local::LocalKey<T>::try_with
                 at /home/im/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:513:12
      25: std::thread::local::LocalKey<T>::with
                 at /home/im/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:477:20
      26: rustpython_vm::vm::thread::set_current_vm
                 at ./src/vm/thread.rs:102:14
      27: rustpython_vm::vm::thread::enter_vm
                 at ./src/vm/thread.rs:132:5
      28: rustpython_vm::vm::interpreter::Interpreter::enter
                 at ./src/vm/interpreter.rs:366:9
      29: rustpython_vm::vm::python_run::tests::test_block_expr_return_const
                 at ./src/vm/python_run.rs:188:23
      30: rustpython_vm::vm::python_run::tests::test_block_expr_return_const::{{closure}}
                 at ./src/vm/python_run.rs:187:38
      31: core::ops::function::FnOnce::call_once
                 at /home/im/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
      32: <fn() -> core::result::Result<(), alloc::string::String> as core::ops::function::FnOnce<()>>::call_once
                 at /rustc/59807616e1fa2540724bfbac14d7976d7e4a3860/library/core/src/ops/function.rs:250:5

[1] 883ce9d273/crates/codegen/src/compile.rs (L2990-L2997)
2026-05-19 13:33:46 +09:00
Shahar Naveh
48ad238349 Update some tests to 3.14.5 (#7909)
* Align patches for `test_class.py`

* Update more tests to 3.14.5

* Update `test_math`

* `test_hash.py`

* Update `test_grammar.py`

* Fix failing test

* Restore `test_grammar.py`
2026-05-19 13:31:33 +09:00
Shahar Naveh
67e66bdc75 code nits (#7908)
* Slight cleanup of super.rs

* frame.rs

* buffer.rs

* bool.rs
2026-05-19 13:31:21 +09:00
Joshua Megnauth
e16f1aa657 Use correct dirs crate; drop uname for rustix (#7906)
RustPython used an ancient, unmaintained version of the `dirs` crate.
This pulled in `winapi-rs` on Windows, which is an old crate that has
been deprecated in favor of `windows-rs`. `windows-rs` is maintained by
Microsoft. Bumping `dirs` to the latest version removes more `winapi-rs`
from Cargo.lock.

As for `uname`, `rustix` handles it safely so the additional crate isn't
needed.
2026-05-19 13:27:32 +09:00
Bas Schoenmaeckers
6e56d935cf Add integer c-api support (#7905)
* Add integer c-api support

* Fix `PyLong_AsUnsignedLongLong` return value

* Fix error message

* Add type check functions
2026-05-19 13:20:43 +09:00
Bas Schoenmaeckers
20cb8843e0 Add unicode & bytes c-api support (#7904)
* Add unicode & bytes c-api support

* Check for negative size
2026-05-19 13:20:19 +09:00
Shahar Naveh
a4579a98b2 Update NOP tests to 3.14.5 (#7902)
* Update `test_xxlimited.py` & `test_xxtestfuzz.py` to 3.14.5

* Add more NOP tests

* Remove `test_capi`

* Remove `test_cext`
2026-05-19 13:20:00 +09:00
Shahar Naveh
883ce9d273 Update test_xpickle.py to 3.14.5 (#7901) 2026-05-18 00:23:34 +09:00
Shahar Naveh
723766ef69 Don't skip passing ast tests (#7900) 2026-05-18 00:23:22 +09:00
Shahar Naveh
3ed8c91fef Update test/support to 3.14.5 (#7896)
* Update `test/support` to 3.14.5

* Adjuat test markers

* Add `test_set` to env polluting tests
2026-05-18 00:23:06 +09:00
Bas Schoenmaeckers
a1a87dc8ca Add macro for type check functions in c-api (#7871)
* Add macro for type check function in c-api

* Add `fn` to macro
2026-05-17 13:04:43 +00:00
Shahar Naveh
ea2a3d9d84 Build workspace cache (#7889) 2026-05-17 20:55:33 +09:00
Shahar Naveh
9c188b4da9 Update ctypes to 3.14.5 (#7899) 2026-05-17 20:54:56 +09:00
Joshua Megnauth
ab72b292bd rustix/windows-sys for page size; drop maplit (#7876)
The `page_size` crate is a simple libc wrapper for Unix and uses
`winapi-rs` for Windows. `windows-sys` and `windows-rs` are the modern
alternatives for the unmaintained `winapi-rs`. Both crates are
maintained by Microsoft - they're official. Getting the page size is a
simple call for both Unix and Windows.

Besides Unix and Windows, I also added the page size for wasm32 which
the `page_size` crate did not support. `wasm32`'s page size is a
constant that is defined by the spec, so I hard coded it without adding
additional dependencies.

Finally, I dropped `maplit` which is seven years old and only used in
one place. Calling `collect()` with a single item iterator is idiomatic
as well as better in this case because Rust can optimize it. `maplit`
called `HashMap::insert` which over allocates to amortize future allocs.
2026-05-17 20:47:13 +09:00
Shahar Naveh
f4f035013d Update test_rlcompleter.py to 3.14.5 (#7898) 2026-05-17 19:50:06 +09:00
Shahar Naveh
c77c56d99c Update venv to 3.14.5 (#7897) 2026-05-17 19:49:53 +09:00
Shahar Naveh
b432d4cbdc Update pickle.py to 3.14.5 (#7895)
* Update `pickle.py` to 3.14.5

* Update pickle deps with new file

* Mark failing tests
2026-05-17 19:49:42 +09:00
Shahar Naveh
5a5230a400 Update shutil.py & zipfile to 3.14.5 (#7894)
* Update `shutil.py` to 3.14.5

* Update `zipfile` to 3.14.5
2026-05-17 19:49:31 +09:00
Shahar Naveh
8c988711dd Update http to 3.14.5 (#7892)
* Update `http` to 3.14.5

* Restore cpu patch
2026-05-17 19:49:19 +09:00
Shahar Naveh
f2ef252724 Update random.py to 3.14.5 (#7890) 2026-05-17 19:49:09 +09:00
Bas Schoenmaeckers
3a56f37505 Add object protocol methods to c-api (#7882) 2026-05-17 19:38:31 +09:00
Shahar Naveh
4b9416a558 Update test_genexps.py & test_metaclass.py to 3.14.5 (#7884)
* Update `test_genexps.py` to 3.14.5

* Align `test_metaclass.py` with the new doctest checker
2026-05-17 01:59:03 +09:00
Shahar Naveh
2d8f8ab818 Move cargo doc to a seperate CI job (#7883) 2026-05-17 01:58:48 +09:00
Bas Schoenmaeckers
a364f86fd5 Add import support to c-api (#7881) 2026-05-17 01:57:47 +09:00
Shahar Naveh
7273d76cf2 Update email to 3.14.5 (#7880)
* Update `email` to 3.14.5

* Update `test_email`

* mark failing tests

* Remove ExtraAssertions
2026-05-17 01:57:05 +09:00
Shahar Naveh
54589bf255 Remove some #[allow(clippy::*)] (#7878)
* Remove some `#[allow(clippy::*)]`

* Fix after merge
2026-05-17 01:56:47 +09:00
dependabot[bot]
0871bc8a2d Bump taiki-e/install-action from 2.75.22 to 2.76.0 (#7838) 2026-05-16 20:00:39 +09:00
Shahar Naveh
f26016049d Update tarfile.py to 3.14.5 (#7879) 2026-05-16 19:41:22 +09:00
dependabot[bot]
451bdcc9da Bump qs and body-parser in /wasm/demo (#7877)
Bumps [qs](https://github.com/ljharb/qs) and [body-parser](https://github.com/expressjs/body-parser). These dependencies needed to be updated together.

Updates `qs` from 6.14.1 to 6.14.2
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.1...v6.14.2)

Updates `body-parser` from 1.20.3 to 1.20.5
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/1.20.5/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.3...1.20.5)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.2
  dependency-type: indirect
- dependency-name: body-parser
  dependency-version: 1.20.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-16 19:02:44 +09:00
Jeong, YunWon
8253253fc7 more hostenv impl (#7604) 2026-05-16 19:01:40 +09:00
Jeong, YunWon
56269f704a Bytecode parity - CFG reorders and LOAD_FAST_BORROW chain (#7870)
* Align LOAD_FAST_BORROW analysis with CPython chain shape

Three changes that bring optimize_load_fast_borrow closer to CPython's
optimize_load_fast in flowgraph.c:

* ir.rs: split mark_cold into the CPython-style two passes. Phase 1
  propagates "warm" from the entry block, phase 2 propagates "cold" from
  except_handler blocks. Blocks reached by neither phase keep cold=false
  and stay in their original b_next position, matching CPython's handling
  of empty placeholders left by remove_unreachable (e.g. the inner_end of
  a nested try/except whose incoming jumps were re-routed by optimize_cfg).

* ir.rs: in optimize_load_fast_borrow, push the fall-through successor
  only when the current block has a last instruction (is_some_and).
  Empty blocks now terminate fall-through propagation, matching the
  `term != NULL` check in optimize_load_fast.

* compile.rs: add switch_to_new_or_reuse_empty() helper and use it in
  compile_while. The helper reuses the current block when it is empty
  and unlinked, mirroring USE_LABEL absorption in
  cfg_builder_maybe_start_new_block. This stops a stray empty block
  from appearing between e.g. a try/except end_block and the following
  while loop header.

Four codegen tests that depended on the previous fall-through-through-
empty behavior are marked #[ignore] with TODO comments.

Also includes a handful of dictionary entries in .cspell.dict picked up
during the work.

* Interleave const fold passes per-block to match CPython

Mirror CPython's optimize_basic_block() (flowgraph.c) by walking each
block once in instruction order and trying tuple, list, set, unary, and
binop folding at each position before advancing. This replaces the
previous global-pass sequence where every fold_unary_constants pattern
in the whole CFG was registered before any tuple constant, leaving
negated literals like `-1` at co_consts positions earlier than CPython
produces (e.g. snippets.py: -1 at idx 280 vs CPython idx 726).

Changes:
- Extract `fold_unary_constant_at` and `fold_binop_constant_at` per-
  position helpers from the existing global passes; the global passes
  now call the helpers in a loop.
- Add `fold_constants_per_block` that walks each block to a fixed point,
  trying all five folds at each instruction position.
- Call the new walker before the legacy global passes in
  optimize_finalize so co_consts insertion order matches CPython's.

Measured on the full Lib tree: differing files 270 → 269; the only
newly matching file is `test/test_ast/snippets.py`, the case raised in
youknowone/RustPython#28.

* Inline small fast-return blocks only through unconditional jumps

`inline_small_fast_return_blocks` previously appended the target
`LOAD_FAST(_BORROW)/RETURN_VALUE` block's instructions onto any
predecessor whose fall-through eventually reached it, in addition to the
unconditional-jump case CPython handles in
`inline_small_or_no_lineno_blocks` (flowgraph.c:1210). CPython only
inlines through unconditional jumps, leaving fall-through predecessors
to reach the shared return block via the natural CFG layout. The extra
fall-through branch duplicated the return tail (e.g. `if/elif/return`
emitted two adjacent `LOAD_FAST_BORROW x; RETURN_VALUE` sequences).

Remove the fall-through inlining branch and keep only the
unconditional-jump path.

Measured on the full Lib tree: differing files 270 → 239 (-31), no new
regressions. Files newly matching include copy.py, argparse.py,
dataclasses.py, logging/__init__.py, pathlib/__init__.py, etc.

* Allow scope-exit/jump-back reorder within a shared except handler

`reorder_conditional_scope_exit_and_jump_back_blocks` previously skipped
any reorder where the conditional, scope-exit, or jump-back block had
an `except_handler` attached, even when all three shared the same
handler. CPython reorders these regardless of try/except context, as
long as the blocks stay within the same protected region. The over-
conservative guard left patterns like `try: for: if cond: return` with
the loop body's scope-exit ahead of the backedge, while CPython places
the backedge first and inverts the conditional.

Replace the `block_is_protected` triple-check with a single
`mismatched_protection` test: skip only when the three blocks do not
share the same `except_handler`. Same-handler reorders preserve the
protected range because every instruction's `except_handler` field
stays attached as `.next` pointers shift.

Measured on the full Lib tree: differing files 239 → 237; no new
regressions.

* Skip jump-over-cleanup reorder when target restarts an exception scope

reorder_jump_over_exception_cleanup_blocks was swapping a small
scope-exit target with a preceding cold cleanup chain even when the
target block began a fresh try (SETUP_FINALLY/SETUP_CLEANUP/SETUP_WITH).
The swap moved the next try's setup ahead of the prior handler's
cleanup_end/next_handler/cleanup_block, making the cleanup_body's
JUMP_FORWARD fall through directly to the cleanup_end and get elided as
redundant. The bytecode then lacked the JUMP_FORWARD that skips the
cleanup blocks and matched the prior handler's borrow tail incorrectly.

Skip the reorder when the target block contains any block-push pseudo
op so a new try's setup stays in source order. Re-enables the four
named/typed except-cleanup borrow tests that were marked #[ignore] in
commit 7481459ea.

* fix if block_idx == BlockIdx::NULL
2026-05-16 17:17:36 +09:00
Joshua Megnauth
078e23de27 Use icu4x for casefold() (#7780) 2026-05-16 12:59:49 +09:00
Shahar Naveh
ddfcb25957 Clippy nursery lints (#7875) 2026-05-15 10:38:36 +00:00
fanninpm
e467b67ef7 Move dependencies to workspace for compiler-core crate (#7874)
* Add dependency from `compiler-core` crate to workspace

* Declare dependency as workspace = true
2026-05-15 16:52:31 +09:00
Shahar Naveh
460b1d39ed Clippy warn uninlined_format_args & redundant_else (#7873) 2026-05-15 16:52:06 +09:00
Bas Schoenmaeckers
11e991fb95 Add basic pyobject c-api functions (#7872) 2026-05-14 13:13:41 +09:00
Joshua Megnauth
ef375bec26 Fix swapcase() (#7788)
The tests for swapcase() were failing for two reasons. The first is
'𐐧' casing which should be fixed with modern Unicode tables. The second
failure is due to CPython's sigma override, which I implemented in
PR #7717.
2026-05-13 23:21:45 +09:00
Bas Schoenmaeckers
4059a032a7 Add basic capi error support (#7787)
* Add basic capi error support

* Add missing symbols to make tests compile again

* Add `pyerrors` to dictionary

* Return exception in `Py_GetConstantBorrowed`

* Remove `allow(dead_code)`

* Fix windows

* Load stdlib when calling `Py_InitializeEx`

* Debug tests

* Revert "Load stdlib when calling `Py_InitializeEx`"

This reverts commit bccd38e981.

* Disable tests on windows for now

* Truncate `PyType_GetFlags` to be always 32 bits

* Add test for exception type checking

* Remove subclass type flags

* Use latest pyo3 to make test work on windows

* Revert "Use latest pyo3 to make test work on windows"

This reverts commit b2c2f6913f.

* `set_main_interpreter` -> `init_main_interpreter`
2026-05-13 23:19:58 +09:00
Shahar Naveh
e8711edd2d Clippy warn on unnecessary wraps (#7869) 2026-05-13 23:14:33 +09:00
Shahar Naveh
f8e0eeb579 Update webbrowser.py to 3.14.5 (#7868) 2026-05-13 19:53:31 +09:00
Shahar Naveh
32e6f8dd81 Update annotationlib.py to 3.14.5 (#7867) 2026-05-13 19:53:18 +09:00
Shahar Naveh
e36cd994e8 Update socket.py to 3.14.5 (#7866)
* Update `socket.py` to 3.14.5

* Don't have socket.sethostname on android

* Mark failing test
2026-05-13 19:52:56 +09:00
fanninpm
7ebffd063b Move dependencies to workspace for wasm crate (#7794)
* Add dependencies from `wasm` crate to workspace

* Declare dependencies as workspace = true
2026-05-13 03:12:22 +00:00
Shahar Naveh
5ef91c22de Unchecked code fixes (#7786)
* Clippy lints fixes

* Update crates/stdlib/src/tkinter.rs

Co-authored-by: fanninpm <27117322+fanninpm@users.noreply.github.com>

* Update crates/stdlib/src/openssl.rs

Co-authored-by: fanninpm <27117322+fanninpm@users.noreply.github.com>

* Update crates/stdlib/src/ssl/compat.rs

Co-authored-by: fanninpm <27117322+fanninpm@users.noreply.github.com>

* Update crates/stdlib/src/ssl/compat.rs

Co-authored-by: fanninpm <27117322+fanninpm@users.noreply.github.com>

* Revert "Update crates/stdlib/src/ssl/compat.rs"

This reverts commit b34228ff37.

---------

Co-authored-by: fanninpm <27117322+fanninpm@users.noreply.github.com>
2026-05-13 09:45:15 +09:00
Shahar Naveh
79395de9f3 Update test_xml_etree.py to 3.14.5 (#7855) 2026-05-12 22:19:57 +09:00
Jeong, YunWon
2b19ba79a8 Align bytecode CFG cleanup with CPython (#7781) 2026-05-12 22:14:24 +09:00
Shahar Naveh
9d77b25858 Add more pedantic clippy rules (#7830) 2026-05-12 20:57:37 +09:00
Shahar Naveh
d09179a7ee Code nits in contextvars.rs (#7835) 2026-05-12 20:57:07 +09:00
Shahar Naveh
8fe23b8a15 Update test_peepholer.py to 3.14.5 (#7864) 2026-05-12 20:56:36 +09:00
Shahar Naveh
ff05dae11c Update pydoc_data to 3.14.5 (#7863) 2026-05-12 20:54:51 +09:00
Shahar Naveh
1da29ff003 Update typing.py to 3.14.5 (#7862) 2026-05-12 20:54:36 +09:00
Shahar Naveh
a702e9ccc7 Update test_functools.py to 3.14.5 (#7861) 2026-05-12 20:54:18 +09:00
Shahar Naveh
9ee27526bc Update encodings to 3.14.5 (#7859) 2026-05-12 20:54:11 +09:00
Shahar Naveh
fb37b5ecc6 Update test_marshal.py to 3.14.5 (#7865)
* Update `test_marshal.py` to 3.14.5

* mark failing tests
2026-05-12 20:53:54 +09:00
Shahar Naveh
3bfa5ab8c0 Update dataclasses.py to 3.14.5 (#7857) 2026-05-12 20:53:35 +09:00
Shahar Naveh
a0632ae2c5 Update inspect.py to 3.14.5 (#7854) 2026-05-12 20:53:24 +09:00
Shahar Naveh
f3c2198ff0 Update timeit.py to 3.14.5 (#7852) 2026-05-12 20:53:11 +09:00
Shahar Naveh
2bcbe96e6d Update test_calendar.py to 3.14.5 (#7848) 2026-05-12 20:53:03 +09:00
Shahar Naveh
f5357692e8 Update test_type_comments.py to 3.14.5 (#7858) 2026-05-12 20:52:52 +09:00
Shahar Naveh
e0689bad7c Update urllib to 3.14.5 (#7856) 2026-05-12 20:52:36 +09:00
Shahar Naveh
1ac55db966 Update test_warnings to 3.14.5 (#7853) 2026-05-12 20:52:20 +09:00
Shahar Naveh
a7c9b98b5a Update test_fileinput.py to 3.14.5 (#7851) 2026-05-12 20:52:08 +09:00
Shahar Naveh
2bb7dd8cf9 Update smtplib.py to 3.14.5 (#7850) 2026-05-12 20:51:57 +09:00
Shahar Naveh
f6c382c595 Update runpy.py to 3.14.5 (#7849) 2026-05-12 20:51:46 +09:00
Shahar Naveh
72679af4b2 Update re related tests (#7847) 2026-05-12 20:51:33 +09:00
Shahar Naveh
27db8e5847 Update test_os.py to 3.14.5 (#7846)
* Update `test_os.py` to 3.14.5

* Mark failing test

* fix typo
2026-05-12 20:51:19 +09:00
Shahar Naveh
68be554684 Update pty.py to 3.14.5 (#7845) 2026-05-12 20:51:02 +09:00
Shahar Naveh
2e5077fe12 Update plistlib.py to 3.14.5 (#7844) 2026-05-12 20:49:48 +09:00
dependabot[bot]
673db1d71a Bump cargo-bins/cargo-binstall from 1.18.1 to 1.19.0 (#7837)
Bumps [cargo-bins/cargo-binstall](https://github.com/cargo-bins/cargo-binstall) from 1.18.1 to 1.19.0.
- [Release notes](https://github.com/cargo-bins/cargo-binstall/releases)
- [Changelog](https://github.com/cargo-bins/cargo-binstall/blob/main/release-plz.toml)
- [Commits](dc19f1e484...4852a15cf0)

---
updated-dependencies:
- dependency-name: cargo-bins/cargo-binstall
  dependency-version: 1.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 20:49:32 +09:00
dependabot[bot]
3cfb0fe7bc Bump j178/prek-action from 2.0.2 to 2.0.3 (#7839)
Bumps [j178/prek-action](https://github.com/j178/prek-action) from 2.0.2 to 2.0.3.
- [Release notes](https://github.com/j178/prek-action/releases)
- [Commits](cbc2f23eb5...6ad8027733)

---
updated-dependencies:
- dependency-name: j178/prek-action
  dependency-version: 2.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 20:49:09 +09:00
dependabot[bot]
9e066c4f50 Bump cranelift in the wasmtime group across 1 directory (#7842)
Bumps the wasmtime group with 1 update in the / directory: [cranelift](https://github.com/bytecodealliance/wasmtime).


Updates `cranelift` from 0.131.0 to 0.131.1
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)

---
updated-dependencies:
- dependency-name: cranelift
  dependency-version: 0.131.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: wasmtime
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 20:48:57 +09:00
dependabot[bot]
3bce5566fa Bump github/gh-aw from 0.71.1 to 0.71.3 (#7841)
Bumps [github/gh-aw](https://github.com/github/gh-aw) from 0.71.1 to 0.71.3.
- [Release notes](https://github.com/github/gh-aw/releases)
- [Changelog](https://github.com/github/gh-aw/blob/main/CHANGELOG.md)
- [Commits](f01a9d118a...2f2a6f572b)

---
updated-dependencies:
- dependency-name: github/gh-aw
  dependency-version: 0.71.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 20:48:42 +09:00
dependabot[bot]
3b67d0f5d2 Bump libz-rs-sys from 0.5.5 to 0.6.0 (#7843)
Bumps [libz-rs-sys](https://github.com/trifectatechfoundation/zlib-rs) from 0.5.5 to 0.6.0.
- [Release notes](https://github.com/trifectatechfoundation/zlib-rs/releases)
- [Changelog](https://github.com/trifectatechfoundation/zlib-rs/blob/main/docs/release.md)
- [Commits](https://github.com/trifectatechfoundation/zlib-rs/compare/v0.5.5...v0.6.0)

---
updated-dependencies:
- dependency-name: libz-rs-sys
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 20:48:28 +09:00
Bas Schoenmaeckers
77070eb6ca Fix ctypes.py_object handling (#7836)
* Fix `ctypes.py_object` handling

* Assume owned pointer
2026-05-12 03:20:01 +09:00
Shahar Naveh
d6abdc4b79 Fixed: unused manifest key: dependencies.libz-sys.package (#7792)
* Fixed: `unused manifest key: dependencies.libz-sys.package`

* Use `libz-rs-sys` instead

* Adjust rust code
2026-05-12 00:54:44 +09:00
Shahar Naveh
fc00fc22d2 Code nits in csv.rs (#7834)
* Code nits in `csv.rs`

* clippy
2026-05-12 00:53:58 +09:00
Shahar Naveh
865e75dd99 Update configparser.py to 3.14.5 (#7833) 2026-05-12 00:53:36 +09:00
Shahar Naveh
9f019a5b86 Update argparse.py to 3.14.5 (#7832) 2026-05-12 00:53:22 +09:00
Shahar Naveh
ad6cc8f0a2 Update test_csv.py to 3.14.5 (#7831)
* Update `test_csv.py` to 3.14.5

* Mark failing test
2026-05-12 00:53:10 +09:00
Shahar Naveh
4a46e84eb9 Add map_unwrap_or clippy rule (#7829) 2026-05-12 00:51:23 +09:00
github-actions[bot]
949a620811 Update doc DB for CPython 3.14.5 (#7828)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-12 00:44:34 +09:00
Shahar Naveh
315865a6f7 Bump targeted CPython version to 3.14.5 (#7826) 2026-05-12 00:44:17 +09:00
Shahar Naveh
c79aefecee Update wsgiref.py to 3.14.4 (#7825) 2026-05-12 00:44:05 +09:00
Shahar Naveh
3fbfbf53c2 Update platform.py to 3.14.4 (#7824) 2026-05-12 00:43:49 +09:00
Shahar Naveh
3bd79e6b5b Update test_site.py to 3.14.4 (#7822) 2026-05-12 00:43:34 +09:00
Shahar Naveh
8e8b70fb33 Update sysconfig to 3.14.4 (#7821)
* Update `test_sysconfig.py` to 3.14.4

* Update `sysconfig` to 3.14.4
2026-05-12 00:43:18 +09:00
Shahar Naveh
29530049fe Update logging to 3.14.4 (#7820)
* Update `test_logging.py` to 3.14.4

* Update `logging` to 3.14.4  /
2026-05-12 00:42:54 +09:00
Shahar Naveh
5b0177d20a Update test_zipimport_support.py to 3.14.4 (#7819)
* Update `test_zipimport_support.py` to 3.14.4

* Add custom doctest checker
2026-05-12 00:42:39 +09:00
Shahar Naveh
479b2dc997 Update datetime related test files to 3.14.4 (#7817)
* Update datetime tests

* Add `test_strptime.py` from 3.14.4
2026-05-12 00:42:10 +09:00
Shahar Naveh
cb0cffbd7c Update test_contextlib_async.py to 3.14.4 (#7816) 2026-05-12 00:41:05 +09:00
Shahar Naveh
cc829b2756 Update test_contextlib.py to 3.14.4 (#7815) 2026-05-12 00:40:32 +09:00
Shahar Naveh
dfe99f647d Reapply patches to test_io.py (#7814) 2026-05-12 00:40:14 +09:00
Shahar Naveh
b3098c3058 Update ensurepip to 3.14.4 (#7811) 2026-05-12 00:39:37 +09:00
Shahar Naveh
8f19dff19d Update test_resource.py to 3.14.4 (#7808)
* Update `test_resource.py` to 3.14.4

* Mark failing tests

* Skip crashing macos test
2026-05-12 00:38:16 +09:00
Shahar Naveh
320355f633 Autogen instructions & opcodes (#7797) 2026-05-10 22:13:38 +09:00
Joshua Megnauth
108461f637 Fix title() and capitalize() (#7717)
* PyBytes.title should be ASCII-only.

* Use icu_casemap over unicode-casing for titles

`icu_casemap` is consistently maintained, official, and tracks the
latest Unicode versions. RustPython is also using other `icu4x` crates,
so using `icu_casemap` is more consistent.

As with islower and isupper, tracking the latest Unicode version is
important because character definitions shift over time which causes
discrepancies between RustPython and CPython.

This commit fixes title().

* Use icu_casemap for capitalize()

I dropped unicode-casing because it's cleaner to use icu4x for
everything. `icu4x` will also stay up to date whereas unicode-casing
will need to be periodically updated with new Unicode tables. Dropping
unicode-casing also removes some binary bloat due to the tables.

`capitalize()` mimics CPython behavior more closely now as well.
Notably, I implemented CPython's sigma edge case handler.

* Match CPython's title() exactly
2026-05-09 18:03:21 +00:00
fanninpm
bf7bb1bf3b Move dependencies to workspace for capi crate (#7795)
* Add dependency from `capi` crate to workspace

* Declare dependency as workspace = true

* Update Cargo.toml

Co-authored-by: Bas Schoenmaeckers <7943856+bschoenmaeckers@users.noreply.github.com>

---------

Co-authored-by: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com>
Co-authored-by: Bas Schoenmaeckers <7943856+bschoenmaeckers@users.noreply.github.com>
2026-05-10 00:31:37 +09:00
Shahar Naveh
09c27bb440 Workaround for random windows cache failure (#7812) 2026-05-10 00:30:54 +09:00
Shahar Naveh
6dfc68b225 Update test_charmapcodec.py to 3.14.4 (#7810) 2026-05-10 00:30:26 +09:00
Shahar Naveh
17376ace28 Update test_global.py to 3.14.4 (#7809) 2026-05-10 00:29:46 +09:00
Shahar Naveh
9eacdfca48 Update test_userlist.py to 3.14.4 (#7807) 2026-05-10 00:29:27 +09:00
Shahar Naveh
68a0bc030b Update test_isinstance.py to 3.14.4 (#7806) 2026-05-10 00:29:05 +09:00
Shahar Naveh
edcf3002de Add test_finalization.py from 3.14.4 (#7805) 2026-05-10 00:28:50 +09:00
Shahar Naveh
67630ffbfe Update test_context.py to 3.14.4 (#7804)
* Update `test_context.py` to 3.14.4

* Mark & fix some tests

* clippy
2026-05-10 00:28:30 +09:00
Shahar Naveh
6ed17c3cb5 Update test_complex.py to 3.14.4 (#7803) 2026-05-10 00:28:13 +09:00
Shahar Naveh
002fc1122e Fix comment confusion bug in update_lib (#7802)
* Use `unittest.expectedSuccess` to mark failing tests

* Fix comment find logic

* Add test for it
2026-05-10 00:27:51 +09:00
dependabot[bot]
ae5c9119c9 Bump fast-uri from 3.0.6 to 3.1.2 in /wasm/demo (#7801)
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.0.6 to 3.1.2.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.0.6...v3.1.2)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-10 00:26:55 +09:00
Joshua Megnauth
4aa73ddab2 Use rustix for uname; drop uname crate (#7799) 2026-05-10 00:26:26 +09:00
Shahar Naveh
7576d68060 Update test_venv.py to 3.14.4 (#7798) 2026-05-10 00:26:05 +09:00
Joshua Megnauth
3bab7a9086 Fix sys.implementation (#7793)
`sys.version_info` and `sys.implementation` are two different versioning
schemes. `version_info` refers to the target Python version while
`implementation` refers to the implementation version.

For RustPython, `version_info` should be 3.14 because it currently
targets Python 3.14. This was correct and remains unchanged.

`implementation`, however, should be set to RustPython's version. I
updated the code to correctly get RustPython's version from the
environment. I also ensured values are hard coded at build time which
reduces the overall code that needs to be run at runtime as well as
removes some unneeded allocations.

Closes: #7770
2026-05-10 00:25:14 +09:00
dependabot[bot]
e10a27b1ae Bump openssl from 0.10.78 to 0.10.79 (#7791)
Bumps [openssl](https://github.com/rust-openssl/rust-openssl) from 0.10.78 to 0.10.79.
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-v0.10.78...openssl-v0.10.79)

---
updated-dependencies:
- dependency-name: openssl
  dependency-version: 0.10.79
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-06 20:44:10 +09:00
fanninpm
3b364608d9 Make determine_changes step run on ubuntu-slim (#7790) 2026-05-06 20:43:55 +09:00
fanninpm
1c4361803d Move dependencies to workspace for vm crate (#7789)
* Add dependencies from `vm` crate to workspace

* Declare dependencies as workspace = true
2026-05-06 14:03:05 +09:00
Bas Schoenmaeckers
22d4f43ad4 Add minimal capi lifecycle support (#7648)
* Add minimal capi lifecycle support

* Force enable `threading` on `stdlib`

---------

Co-authored-by: Jeong, YunWon <jeong@youknowone.org>
2026-05-06 02:02:26 +09:00
Shahar Naveh
02a2b19839 Remove unused rust impl for formatting dis output (#7660)
* Remove unused rust impl for formatting dis output

* remove `examples/dis.rs`

* Added tests

* Update lock

* Try to set snapshot dir

* Remove verbose flag

* Regenerate snapshots after #7711

* Revert "Bump insta from 1.46.3 to 1.47.2 (#7706)"

This reverts commit e6d9ea6bfe.

* Debug info

* Show diff as well

* Show debug faster

* CI: true env

* Recert CI

* Add `CI: true` in ci emv

* Reapply "Bump insta from 1.46.3 to 1.47.2 (#7706)"

This reverts commit 693ca8cbe4d7885a81162a9be31e8bb567db885a.

* simplify macro

* trim on function side

* Force insta workspace root

* fix merge
2026-05-05 08:09:35 +00:00
Jeong, YunWon
ae7ff9c481 codegen: module seed CodeInfo emits empty flags (#7782)
CPython convention: top-level module / interactive / expression code
does not carry CO_NEWLOCALS or CO_OPTIMIZED. The per-scope mapping at
enter_scope::CompilerScope::Module already returns empty flags, but
Compiler::new seeded the root CodeInfo with CodeFlags::NEWLOCALS,
forcing module code into the NEWLOCALS arm of frame.rs:725-731 so
locals were allocated as a fresh empty dict instead of being bound to
globals (the correct semantics for exec(code, globals)).

Restore the seed to empty() so it matches the per-scope mapping and
CPython's compiler_enter_scope for module scope.
2026-05-05 16:22:55 +09:00
dependabot[bot]
d877c30417 Bump https://github.com/astral-sh/ruff-pre-commit (#7779)
Bumps [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit) from v0.15.11 to 0.15.12.
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.11...v0.15.12)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.15.12
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-05 14:25:08 +09:00
dependabot[bot]
8bd4c5137e Bump mwilliamson/setup-wabt-action from 3.2.0 to 4.0.0 (#7778)
Bumps [mwilliamson/setup-wabt-action](https://github.com/mwilliamson/setup-wabt-action) from 3.2.0 to 4.0.0.
- [Release notes](https://github.com/mwilliamson/setup-wabt-action/releases)
- [Commits](https://github.com/mwilliamson/setup-wabt-action/compare/v3.2.0...427f2fdd70bc4dbc2e53c2eb4f19f66162d71bd2)

---
updated-dependencies:
- dependency-name: mwilliamson/setup-wabt-action
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-05 14:24:34 +09:00
Shahar Naveh
dc12bff0f4 Move clippy and cargo-shear to a dedicated job (#7741) 2026-05-05 14:24:21 +09:00
dependabot[bot]
e1dd3d43d5 Bump github/gh-aw from 0.68.3 to 0.71.1 (#7776)
Bumps [github/gh-aw](https://github.com/github/gh-aw) from 0.68.3 to 0.71.1.
- [Release notes](https://github.com/github/gh-aw/releases)
- [Changelog](https://github.com/github/gh-aw/blob/main/CHANGELOG.md)
- [Commits](ce1794953e...f01a9d118a)

---
updated-dependencies:
- dependency-name: github/gh-aw
  dependency-version: 0.71.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-05 14:22:14 +09:00
dependabot[bot]
a5cebc3242 Bump the wasmtime group across 1 directory with 2 updates (#7775)
Bumps the wasmtime group with 1 update in the / directory: [cranelift-jit](https://github.com/bytecodealliance/wasmtime).


Updates `cranelift-jit` from 0.131.0 to 0.131.1
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)

Updates `cranelift-module` from 0.131.0 to 0.131.1
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)

---
updated-dependencies:
- dependency-name: cranelift-jit
  dependency-version: 0.131.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: wasmtime
- dependency-name: cranelift-module
  dependency-version: 0.131.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: wasmtime
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-05 14:22:02 +09:00
Jeong, YunWon
ad5a55c1e3 Bytecode parity: align borrow optimization with CFG traversal (#7773)
* Align CFG cleanup bytecode with CPython

* Bytecode parity: fblock unwind, fstring join, folding, scope

- compile.rs: unwind_fblock_stack returns whether a finally ran so
  return-statement emission can adjust location handling; restructure
  try/except/finally cleanup to preserve or drop boundary NOPs based on
  whether the body falls through; rework f-string lowering with
  count/join helpers; remove the per-collection-type heuristic for
  AST-level folding and defer to flowgraph passes; add several folding
  helpers and a ComprehensionLoopControl enum.
- ir.rs: re-run unary/binop folding around tuple folding, add
  reorder_conditional_scope_exit_and_jump_back_blocks and several block
  classification helpers, add MAX_STR_SIZE, change is_exit_without_lineno
  to take the block list.
- symboltable.rs: in analyze_cells, remove names owned as cells in
  function-like scopes from the parent's free set; mark lambda scope
  type explicitly.

* Refine CFG scope-exit backedge ordering
2026-05-05 14:04:12 +09:00
fanninpm
e4d35b08ea Move dependencies to workspace for common crate (#7771)
* Add dependencies from `common` crate to workspace

* Declare dependencies as workspace = true
2026-05-05 14:02:57 +09:00
Noa
02932384d6 Use cfg_select in a bunch more places (#7740) 2026-05-04 20:26:16 +00:00
Shahar Naveh
0325fd429e Opcode name (#7433) 2026-05-04 20:49:47 +09:00
Shahar Naveh
4db0aca471 fix ci (#7774)
* fix ci

* more
2026-05-04 20:41:08 +09:00
Changjoon
83002d7369 Tighten CPython parity for str format spec, %-format, and str() constructor (#7769)
Five related CPython parity gaps in `str` formatting and construction:

1. **`str(bytes, errors=...)` triggers decode mode.** Previously, only
   `encoding=` triggered decode; passing only `errors=` fell back to
   `repr()`. CPython's behavior: presence of `encoding` OR `errors`
   triggers decode mode (default UTF-8 when only `errors` is given).

2. **`'{...}'.format() IndexError wording.** Generic Rust "tuple index
   out of range" replaced with CPython's "Replacement index N out of
   range for positional args tuple".

3. **`{0:3.2s}.format('abc')` → 'ab '.** String format spec applied
   precision after width padding; CPython truncates BEFORE padding.
   Reorder the operations.

4. **`%x` / `%o` / `%X` / `%c` accept `__index__` objects.** Previously
   only `PyInt` downcast was attempted. Mirror CPython's
   PyNumber_Index dispatch via `try_index_opt`.

5. **`%d` / `%u` / `%i` error wording.** "a number is required" →
   "a real number is required" (matches CPython).

Also adds `not <type>` suffix to `%c` error messages so the type is
visible in TypeError text (matches CPython structure even without
fully-qualified names).

Verified byte-identical with CPython 3.14.4 across 25+ probes covering
the format/spec/constructor combinations. Unmasks
`test_str.test_constructor_keyword_args` and
`test_str.test_constructor_defaults`. test_str/test_bytes/test_format/
test_codecs/test_io/test_unicode_identifiers — 1,429 tests pass, 0
regressions. All 188 `extra_tests/snippets/*.py` pass under the CI
feature set.

`test_str.test_format` and `test_str.test_formatting` markers retained:
`test_format` still trips on `'{0:08s}'.format('result')` (numeric
zero-pad treated as fill+left-align by CPython for str type — separate
format-spec parser concern). `test_formatting` still trips on
`%c` error message expecting fully qualified `module.qualname` (RP
returns bare class name — separate broader concern).
2026-05-04 10:28:43 +09:00
Changjoon
90cc888f4b Validate compile() filename type and dont_inherit __bool__ protocol (#7768)
Two CPython parity gaps in `compile()` argument handling:

1. **filename rejects only non-buffer-protocol types.** CPython's
   `PyUnicode_FSDecoder` accepts `str`, `bytes`, and objects with
   `__fspath__` only — `bytearray` / `memoryview` / `array.array` raise
   TypeError. RustPython's `FsPath::TryFromObject` impl falls back to
   any buffer-protocol object and silently converts to bytes, accepting
   them.

   ```python
   >>> compile('pass', bytearray(b'file.py'), 'exec')
   # CPython 3.14.4: TypeError
   # RustPython main: <code object>  
   ```

   Change `CompileArgs::filename` from `FsPath` (which uses the
   permissive `TryFromObject` impl) to `PyObjectRef`, then call the
   strict `FsPath::try_from_path_like` at the top of `compile()`. Other
   `FsPath` consumers are unchanged.

2. **dont_inherit strict-checks the `bool` type.** CPython routes
   `dont_inherit` through `PyObject_IsTrue`, calling `__bool__` on
   arbitrary objects and propagating exceptions raised there. RustPython
   typed it `OptionalArg<bool>`, which rejects subclass-less objects at
   binding time before `__bool__` runs.

   ```python
   >>> class EvilBool:
   ...     def __bool__(self): raise ValueError('hi')
   >>> compile('pass', 'f', 'exec', dont_inherit=EvilBool())
   # CPython 3.14.4: ValueError('hi')
   # RustPython main: TypeError('Expected type bool, not EvilBool')  
   ```

   Switch to `OptionalArg<ArgIntoBool>` — already used elsewhere in
   `builtins` (`all`, `any`, `print(..., flush=...)`).

Verified byte-identical with CPython 3.14.4 across:
- 6 filename types (str, bytes, bytearray, memoryview, list, int)
- 7 dont_inherit values (True/False/1/0/""/"yes"/None) plus the
  `__bool__`-raises-ValueError case

Unmasks `Lib/test/test_compile.py`:
- `test_compile_filename`
- `test_compile_filename_refleak`

`cargo run --release -- -m test test_compile test_builtin test_compileall
test_codeop test_ast` — 703 tests pass, 0 regressions. All 188
`extra_tests/snippets/*.py` pass under the CI feature set.
2026-05-04 10:27:53 +09:00
Jiseok CHOI
cf23884656 test(str): add regression test for isprintable() unicode 15 chars, (#7766)
Fixes #7525
2026-05-04 10:27:19 +09:00
Changjoon
c3c9292c8b Match CPython wording for compile() optimize and flags validation (#7765)
CPython's `builtin_compile_impl` (Python/bltinmodule.c) accepts only
optimize ∈ {-1, 0, 1, 2}; anything else raises
`ValueError("compile(): invalid optimize value")`. The previous logic
only validated via `i32::try_into::<u8>()`, which silently accepts every
value in [0, 255], so `compile(..., optimize=3)`, `optimize=99`, etc.
were silently truncated to a u8. The error wording also had the wrong
word order.

Replace the cast-based check with a `match` against the spec range.

Adjacent: the unrecognised-flags message used American spelling
("unrecognized") and missed the colon separator. CPython uses British
"unrecognised" with a colon — match it.

Verified byte-identical with CPython 3.14.4 across 12 boundary values
for optimize and 4 cases for flags. Preserves the existing OverflowError
path for `optimize=1 << 1000` (raised at the ArgPrimitiveIndex<i32>
conversion layer, before this check).
2026-05-04 10:26:16 +09:00
Shahar Naveh
ee5e9d0001 Enable some pedantic clippy lints (#7764) 2026-05-04 10:25:37 +09:00
Changjoon
181e4e7124 Drop redundant exception type prefix from float-to-int error messages (#7763)
CPython raises `OverflowError("cannot convert float infinity to integer")`
and `ValueError("cannot convert float NaN to integer")` from
`Objects/floatobject.c::float___trunc___impl` and friends. The exception
type name is added by Python's traceback display layer; the message
itself should not duplicate it.

`try_to_bigint` was producing
`OverflowError("OverflowError: cannot convert ...")` etc., which made
`repr(e)` and any code path that inspects `str(e)` diverge from CPython.

Affects all 5 callers of `try_to_bigint`: `__int__`, `__floor__`,
`__ceil__`, `__round__` (no-arg), `__trunc__` — i.e. `int(x)`,
`math.floor/ceil/trunc(x)`, `round(x)` for non-finite floats.

Verified byte-identical with CPython 3.14.4 across 14 affected sites.
2026-05-04 10:24:08 +09:00
Shahar Naveh
eb99a8ecf1 Warn on unreachable_pub (#7762) 2026-05-04 10:18:24 +09:00
fanninpm
acc167fc44 Move dependencies to workspace for stdlib crate (#7747)
* Add dependencies from `stdlib` crate to workspace

* Declare dependencies as workspace = true
2026-05-04 09:50:21 +09:00
Changjoon
c2910c06f3 Round float at the decimal level to match CPython's _Py_dg_dtoa (#7761)
* Round float at the decimal level to match CPython's _Py_dg_dtoa

CPython's `float.__round__` (Objects/floatobject.c) routes through
`_Py_dg_dtoa` and rounds at the decimal level. The previous
`round_float_digits` multiplied by 10**ndigits and rounded at the
IEEE 754 binary level, which diverges for values that aren't exactly
representable. For example, 2.675 stores as 2.67499...; dtoa correctly
rounds it down to 2.67, but `(2.675 * 100.0).round() / 100.0` lands on
2.68 because the multiplication produces a phantom 267.5 tie that
round-half-to-even snaps up.

Rust's `{:.*}` float formatting uses dtoa-style algorithms (Grisu3 +
Dragon4 fallback) and matches CPython's `_Py_dg_dtoa` byte-for-byte.
Replace the multiply-then-round step with `format!` + `parse` for
ndigits >= 0. The ndigits < 0 path is unchanged because dividing
typical inputs by 10**|ndigits| produces genuine ties rather than
synthesizing them.

Verified byte-identical with CPython 3.14.4 over a 108-case random
fuzz plus targeted half-tie probes. Unmasks
`test_float.RoundTestCase.test_matches_float_format` and
`test_previous_round_bugs`.

* Use #[expect] with reason for float_cmp suppression

Co-authored-by: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com>

---------

Co-authored-by: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com>
2026-05-03 00:16:27 +09:00
Shahar Naveh
ac3e067230 Only run cargo check when rust code is changed (#7760)
* Only run `cargo check` when rust code is changed

* Test

* Another test

* Revert "Test"

This reverts commit 1f23cc33f5.

* Revert "Another test"

This reverts commit 50a51d5e56.

* Add extra rust related files
2026-05-03 00:15:46 +09:00
Shahar Naveh
be7841f9c1 Update test_py_compile.py to 3.14.4 (#7759) 2026-05-03 00:15:22 +09:00
Shahar Naveh
3e66fb508a Update str related tests (#7758)
* Update `string_tests.py` to 3.14.4

* Update `test_bytes.py` to 3.14.4

* Update `test_userstring.py` to 3.14.4

* Mark failing tests
2026-05-03 00:15:03 +09:00
Shahar Naveh
f2ad84a489 AnyInstruction helpers + doc imrovments (#7757) 2026-05-03 00:13:31 +09:00
Shahar Naveh
926d69b50a Add some clippy lints (#7755) 2026-05-02 11:14:23 +09:00
dependabot[bot]
8d1c68c9a0 Bump actions/setup-node from 6.3.0 to 6.4.0 (#7689)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.3.0 to 6.4.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](53b83947a5...48b55a011b)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-02 08:16:15 +09:00
Shahar Naveh
d9fff99718 Pin actions to hash (cron-ci.yaml) (#7686) 2026-05-02 08:15:43 +09:00
Shahar Naveh
0f25d145fd Make updating actions cache synchronous (#7712) 2026-05-02 08:15:14 +09:00
Shahar Naveh
7fd0da92d3 Fix libs deps check (#7732)
* Convert parsing logic to python

* Trigger job. (DROP ME BEFORE MERGE!)

* Missing EOF

* Add found modules to set

* Suggestion by @fanninpm

* Fix missing trailing slash

* Ensure no `.py` at module name

* Strip any file auffix

* Revert "Trigger job. (DROP ME BEFORE MERGE!)"

This reverts commit 8cf9651a24.

* Handle quoted args
2026-05-02 08:14:55 +09:00
Shahar Naveh
c027ffc2ba Pin marocchino/sticky-pull-request-comment action to a hash (#7751) 2026-05-01 22:58:35 +09:00
Jiseok CHOI
192ba371e4 Fix function attribute tests: __defaults__ del, __code__ free vars ch… (#7749)
* Fix function attribute tests: __defaults__ del, __code__ free vars check, bound method dir()

- Support `del func.__defaults__` and `del func.__kwdefaults__` by changing
  setter signatures to `PySetterValue<Option<...>>` so Delete is treated as
  setting the value to None (matching CPython behaviour)
- Validate free-variable count when assigning to `func.__code__`: raise
  ValueError when the new code object's freevars count doesn't match the
  existing closure size
- Add `__dir__` to `PyBoundMethod` that delegates to the underlying
  function's dir(), so attributes set on the function show up in
  `dir(bound_method)`
- Remove `@unittest.expectedFailure` from the four tests that now pass:
  test_blank_func_defaults, test_func_default_args, test___code__,
  test_dir_includes_correct_attrs

* Remove expectedFailure markers for now-passing tests
2026-05-01 22:58:16 +09:00
Shahar Naveh
d25195ed0e Update test_binascii.py to 3.14.4 (#7753)
* Update `test_binascii.py` to 3.14.4

* Mark failing test
2026-05-01 12:18:19 +00:00
Shahar Naveh
9004089fd1 Update unittest to 3.14.4 (#7752)
* Update `unittest` from 3.14.4

* Update `test_unittest` to 3.14.4

* Unmark passing test
2026-05-01 11:06:24 +00:00
dependabot[bot]
f6e2fcffe7 Bump liblzma-sys from 0.4.5 to 0.4.6 (#7744)
Bumps [liblzma-sys](https://github.com/portable-network-archive/liblzma-rs) from 0.4.5 to 0.4.6.
- [Release notes](https://github.com/portable-network-archive/liblzma-rs/releases)
- [Commits](https://github.com/portable-network-archive/liblzma-rs/compare/liblzma-sys-0.4.5...liblzma-sys-0.4.6)

---
updated-dependencies:
- dependency-name: liblzma-sys
  dependency-version: 0.4.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 19:49:10 +09:00
Shahar Naveh
bb77ac6284 Update test_unicodedata.py to 3.14.4 (#7750)
* Update `test_unicodedata.py` to 3.14.4

* Add test asset

* Mark failing tests

* Add `expectedSuccess` docorator

* Add support for nee decorator in patch_spec

* Reapply patches
2026-05-01 19:48:41 +09:00
Changjoon
c2141a765f Apply titlecase mapping in str.title() for uppercase digraphs (#7748)
The uppercase/titlecase branch of PyStr::title() pushed characters
unchanged when starting a new word, which left Latin Extended-B
digraphs (U+01F1 'DZ', U+01C4 'DŽ', etc.) in their uppercase form
instead of mapping them to their distinct titlecase counterparts
(U+01F2 'Dz', U+01C5 'Dž'). For ASCII letters and characters where
to_titlecase is identity this had no effect, hiding the bug for the
common case.

Mirror the lowercase branch — which already calls to_titlecase()
when starting a new word — so both branches symmetrically apply
the titlecase mapping. char::to_titlecase is identity for already-
titlecase and ASCII-uppercase characters, so existing cases stay
correct.

Also unmasks test_unicodedata.UnicodeMiscTest.test_bug_4971, which
asserts exactly this behavior (`'DŽ'.title() == 'Dž'` etc.)
and was marked expectedFailure with reason `+ Dž`.

Closes #7527 (the only example from that issue still failing on
3.14.4; the other four examples already pass on current main).
2026-05-01 19:45:55 +09:00
Changjoon
dd1cbac692 Match CPython's _float_div_mod, fixing divmod and % zero-handling (#7745)
float_ops::divmod, mod_, and floordiv each carried their own conversion
from Rust's dividend-sign `%` to CPython's divisor-sign convention. Both
divmod and mod_ mishandled the zero-remainder case where the dividend
is a non-zero exact multiple of the divisor (e.g. divmod(6.0, -3.0),
6.0 % -3.0): the sign-correction branch fired on a zero remainder and
produced (-3.0, -3.0) and -3.0 respectively, violating the magnitude
invariant 0 <= abs(r) < abs(b). divmod also leaked the wrong signed-
zero quotient when the true quotient was zero (divmod(-1.0, -2.0)
returned (-0.0, -1.0) instead of (+0.0, -1.0)).

These are independent bugs in two functions, but both come from the
same root cause: zero-remainder needs a separate path from the sign-
correction branch.

Mirror CPython's `_float_div_mod` (Objects/floatobject.c) by making
divmod the canonical implementation and turning mod_ and floordiv into
thin wrappers. divmod(a, b) == (a // b, a % b) now holds by
construction.

Closes #7722
2026-05-01 19:45:28 +09:00
Changjoon
c98d26e508 Raise OverflowError in float pow when finite-base result overflows (#7746)
PR #7727 added an overflow guard in float_pow's negative-base
non-integer-exponent branch (which delegates to complex_pow). The
remaining else branch — covering positive base or negative-base
integer exponent — directly returned v1.powf(v2) without inspecting
the result, so finite inputs that produced an out-of-range value
silently leaked f64::INFINITY (or -INFINITY) instead of raising.

Examples that now raise OverflowError as in CPython:
    pow(2.0, 2000)
    pow(10.0, 400)
    pow(-2.0, 2001)
    pow(0.5, -2000)
    pow(1e150, 3)

Mirror the inline overflow guard already used in complex.rs::complex_pow:
when the result is infinite but neither input is, raise OverflowError.
The both-finite check preserves intentional infinities like
pow(inf, 2.0).

Closes #7729
2026-05-01 19:44:42 +09:00
Changjoon
5e408d65f4 Resolve PathLike via os.fspath in io.open (#7743)
CPython splits PathLike resolution across two layers: _io.FileIO
preserves the raw argument on .name (low-level contract), while
the high-level open() wrapper resolves it via os.fspath before
delegating. RustPython's io_open skipped that step, so
builtins.open(PathLike).name returned the PathLike object instead
of the resolved str/bytes.

Match Lib/_pyio.py:193-194 by resolving non-int inputs at the
io_open boundary, leaving _io.FileIO unchanged.
2026-05-01 19:44:10 +09:00
Shahar Naveh
73b5b69bd8 Cargo check features on all os (#7733)
* Cargo check features on all os

* Adjust cargo conf

* Skip unsupported ssl platforms

* zizmor

* Remove duplicated check
2026-05-01 19:43:43 +09:00
Jeong, YunWon
e79df4a6fb Bytecode parity CFG normalization and jump cleanup (#7721)
* typealias reviews

* Bytecode parity - try/except block order, CFG reorder

Reorder try/except/else/finally to emit else+finally before
except handlers matching CPython layout. Add set_no_location
for cleanup blocks. Extend CFG reorder pass to handle true-path
jump-back for generators, break/continue, and assert in loops.
Add stop-iteration error handler awareness to block protection.

* Align CFG cleanup bytecode with CPython

* Unmark test_dis.test_findlabels expected failure

* Compute target predecessor flags in single pass

remove_nops and remove_redundant_nops_in_blocks repeated
has_jump_predecessor / has_plain_jump_predecessor / target lookups
per block, scanning all blocks each time. With ~200,000 if blocks
this became O(B^2 * I) and timed out test_compile.test_stack_overflow.
Fold the three flags into one O(B * I) pass via
compute_target_predecessor_flags.

* Address review feedback on slice folding, fallthrough, attr-chain

- try_fold_constant_slice now delegates to try_fold_constant_expr so
  slice bounds accept the same constants other folding paths do
  (unary-folded values, __debug__, etc.).
- remove_nops resolves fallthrough predecessors through empty blocks
  via next_nonempty_block before checking ends_with_for_cleanup.
- should_deopt_borrowed_attr_chain ReturnIter matcher now accepts
  Instruction::CallKw alongside Instruction::Call, matching the
  Call/CallKw treatment in the surrounding deopt trigger check.

* remove test
2026-04-30 23:30:27 +09:00
Shahar Naveh
32e16fe7da Fix possible npm cache poisoning (#7742)
* Fix possible npm cache poisoning

* fix typo

* fix logic errors

* Use correct output
2026-04-30 23:21:06 +09:00
fanninpm
88be7bb16a Move dependencies to workspace for literal crate (#7739)
* Add dependencies from `literal` crate to main workspace

* Declare dependencies as workspace = true
2026-04-30 10:24:50 +03:00
Alok Pandey
f90a5cf650 Fix del obj.__dict__ to match CPython behavior (fixes #5355) (#7568)
* Fix del obj.__dict__ to match CPython behavior (issue #5355)

* Address CodeRabbit concerns: fix GC clearing and improve thread safety of lazy __dict__ recreation

* Fix del obj.__dict__: improve GC safety, implement lazy re-creation in setattr, and enable passing CPython tests

* Restore expectedFailure for test_has_inline_values

* Fix ObjExt::new call site to include has_dict parameter

* Remove stray test.py to avoid CI syntax errors

* Remove debug txt files and clean test_class.py comments

* Delete Lib/test/test_class.py

* Restore test_class.py with correct changes (remove expectedFailure, no deletion)

* Fix clippy warnings: remove unused into_inner, collapse nested if-let

* Fix rustfmt formatting and ruff PEP 8 E302 blank line

* Align __dict__ error messages and ensure safety for function/partial objects

* Fix compilation errors: change &self to &Py<Self> in __dict__ methods

* Fix compilation errors: resolve borrow-after-move and replace transpose on PySetterValue

- type.rs: Replace invalid .transpose() on PySetterValue with explicit
  match on Assign/Delete variants in subtype_set_dict
- function.rs: Fix borrow-after-move in set___dict__ by capturing class
  name before downcast; use as_object() for instance_dict/set_dict calls
- _functools.rs: Same borrow-after-move fix and as_object() calls for
  PyPartial's __dict__ getter/setter

* Fix compilation errors: resolve borrow-after-move and replace transpose on PySetterValue

* Fix snippet formatting and mark test_remote as expected failure

* Fix test_remote by removing HAS_DICT flag from function type

* Fix lint formatting error

* Remove unnecessary print statement in test_del_dict

* Fix trailing newlines in snippet test

* Trigger CI

* Align __dict__ generic setter behavior

* Move __dict__ deletion tests to relevant snippets

---------

Co-authored-by: Jeong, YunWon <jeong@youknowone.org>
2026-04-30 02:09:16 +00:00
dependabot[bot]
6c2c8421d7 Bump actions/upload-artifact from 7.0.0 to 7.0.1 (#7640)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](bbbca2ddaa...043fb46d1a)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-30 11:01:47 +09:00
dependabot[bot]
6e895fbac4 Bump psm from 0.1.30 to 0.1.31 (#7738)
Bumps [psm](https://github.com/rust-lang/stacker) from 0.1.30 to 0.1.31.
- [Commits](https://github.com/rust-lang/stacker/commits)

---
updated-dependencies:
- dependency-name: psm
  dependency-version: 0.1.31
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-30 04:45:57 +09:00
Shahar Naveh
543fcc841c Update test_weakset.py to 3.14.4 (#7735) 2026-04-30 04:45:43 +09:00
Changjoon
6c91c5bb2a Short-circuit identity in rich_compare_bool for Eq/Ne (PyObject_RichCompareBool parity) (#7734)
* Short-circuit identity in rich_compare_bool for Eq/Ne (PyObject_RichCompareBool parity)

CPython distinguishes two comparison entry points (Objects/object.c):
- PyObject_RichCompare returns the raw __eq__ / __ne__ result; no
  identity short-circuit
- PyObject_RichCompareBool returns bool; identity implies equality
  (and inequality is false on identity), short-circuiting before
  dispatch

Collection membership / equality (x in [x], [nan] == [nan], set/dict
comparisons) go through the bool variant and rely on the short-circuit.
RustPython's rich_compare_bool skipped the identity check, so a buggy
or raising __eq__ propagated even when the operand was the same object.

Add an identity short-circuit at the top of rich_compare_bool for Eq
(returns true) and Ne (returns false). Ordering ops fall through to
_cmp because Python does not guarantee reflexivity for </<=/>/>=.
_cmp itself is untouched, so == / != operators continue to invoke
__eq__ / __ne__ exactly as before.

Unmasks test_dictviews.TestDictViews.test_compare_error.

Verified byte-identical with CPython 3.14.4 across 53 scenarios in 10
categories (collection membership / equality / ordering ops / NaN /
hash collision / dict views / list-set-dict ops). 14-module regression
sweep ~2,402 tests passes with no regressions.

* Route proxy comparisons through PyObject_RichCompare

The identity short-circuit added to rich_compare_bool exposed a latent
bug in three proxy types (weakref, weakproxy, mappingproxy): they were
delegating their __eq__ to the bool variant on referents, while CPython
uses PyObject_RichCompare so the referent's __eq__ runs even when the
referents share identity.

Fixes test_weak_keyed_cascading_deletes which depends on key __eq__
firing during dict deletion to trigger a side-effect that mutates the
key list.
2026-04-30 04:45:23 +09:00
Shahar Naveh
3c297d478a Update test_call.py to 3.14.4 (#7730) 2026-04-30 04:40:44 +09:00
Jeongseop Lim
6ed2f15b67 Raise OverflowError in float pow when complex result overflows (#7727)
* fix: Align error messages with CPython

* fix: Raise overflow error on complex exponentiation overflow

* refactor: Delegate complex pow handling from float pow

* test: Add regression tests for complex pow overflow

---------

Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com>
2026-04-30 04:40:31 +09:00
Changjoon
6b67067e9a Chain __context__ alongside __cause__ when wrapping StopIteration (PEP 479) (#7731)
PEP 479 wraps a StopIteration raised from a generator/coroutine into a
RuntimeError. CPython sets both __cause__ and __context__ of the new
RuntimeError to the original StopIteration (the same object). RustPython's
wrapping sites set __cause__ only, leaving __context__ as None.

set___cause__ already toggles __suppress_context__ to true, so the
user-facing traceback is unchanged — only assertions that inspect
__context__ directly observe the gap.

Three sites all wrap StopIteration / StopAsyncIteration into RuntimeError
for PEP 479. Set __context__ to the same exception instance before
setting __cause__ at each:

- frame.rs::StopIterationError intrinsic (generator yield-expression path)
- coroutine.rs generator __next__ StopIteration branch
- coroutine.rs async-generator __anext__ StopAsyncIteration branch

PyBaseExceptionRef is Arc-backed, so e.clone() is a refcount bump;
'cause is context' holds, matching CPython.

Unmasks:
- test_generator_stop.TestPEP479.test_stopiteration_wrapping_context
- test_yield_from.TestInterestingEdgeCases.test_close_and_throw_work
- test_yield_from.TestInterestingEdgeCases.test_close_and_throw_raise_stop_iteration
- test_yield_from.TestInterestingEdgeCases.test_close_and_throw_yield
2026-04-29 18:52:22 +09:00
Changjoon
04ffa3891c Match CPython's "argument of type ... is not a container or iterable" wording (#7714)
CPython's PySequence_Contains (Objects/abstract.c::_PySequence_IterSearch)
catches the TypeError from PyObject_GetIter when neither __contains__ nor
__iter__ is available, and re-raises with membership-test wording.

RustPython's PySequence::contains propagated the get_iter error verbatim,
so '1 in obj' produced 'X' object is not iterable — a literal but less
specific message.

Wrap the get_iter call in map_err: when the failure is a TypeError,
re-raise with byte-identical CPython wording. Other exception types pass
through unchanged.

Unmasks test_contains.TestContains.test_common_tests.
2026-04-29 18:29:31 +09:00
Changjoon
ba2b619c0c Accept __index__-conforming objects for compile() flags / optimize (#7728)
CPython's compile() (Python/Python-ast.c) accepts any object with
__index__ for the flags and optimize arguments. RustPython's CompileArgs
typed both fields as OptionalArg<PyIntRef>, so a class with only
__index__ raised 'TypeError: Expected type int but X found' during arg
binding.

bpo-36907's regression test (test_call.test_fastcall_clearing_dict)
exercises exactly this: an IntWithDict.__index__ that mutates
self.kwargs mid-call. CPython parses both args via __index__ and doesn't
crash even when the kwargs dict is cleared between binding and use.

Switch flags and optimize to OptionalArg<ArgPrimitiveIndex<i32>>, the
same helper already used by range, slice, bytes.__mul__, hex, oct, etc.
ArgPrimitiveIndex calls try_index (= __index__ protocol) and converts
to the requested primitive in one step, so the three call sites in
compile() simplify from .map_or(Ok(d), |v| v.try_to_primitive(vm))? to
.map_or(d, |v| v.value).

Unmasks test_call.FastCallTests.test_fastcall_clearing_dict.
2026-04-29 18:26:49 +09:00
Jeongseop Lim
c8ddbd2326 Fix pow() raising ValueError instead of TypeError for non-int exponent (#7725)
* Validate pow() exponent type before zero-modulus check

* Add tests for pow() exponent type check
2026-04-29 18:18:11 +09:00
Changjoon
3ebcab70c0 Make mappingproxy's nb_or slot symmetric for dict | mp (#7723)
* Make mappingproxy's nb_or slot symmetric for dict | mp

CPython's mappingproxy_or (Objects/descrobject.c) unwraps a mappingproxy
on either side of '|' to its underlying mapping and delegates back to
PyNumber_Or. This makes 'dict | mp', 'mp | dict', and 'mp | mp' all
produce a plain dict result without dict's own nb_or having to know
about mappingproxy.

RustPython's 'or' slot in 'AsNumber for PyMappingProxy' only handled
the case where the left operand is a mappingproxy. When the slot fired
with a=dict, b=mp (because dict.nb_or returned NotImplemented), it
returned NotImplemented again — so 'dict | mp' raised TypeError. This
propagated to 'UserDict | mappingproxy' (which calls self.data | other).

Rewrite the slot to unwrap a mappingproxy on either side (or both)
before delegating to vm._or on the underlying mappings. The
inplace_or slot is unchanged — '|=' is still rejected.

Unmasks test_userdict.UserDictTest.test_mixed_or.

* Unmask test_types.MappingProxyTests.test_union

The slot fix in this PR also enables this test, which was marked
expectedFailure for the same dict|mappingproxy TypeError. Caught as
UNEXPECTED SUCCESS in CI.
2026-04-29 18:16:27 +09:00
fanninpm
51e7200d11 Move dependencies to workspace for jit crate (#7720)
* Add dependencies from `jit` crate to main workspace

* Declare dependencies as workspace = true
2026-04-29 18:15:01 +09:00
fanninpm
d7a319d967 Move dependency to workspace for host_env crate (#7719)
* Add dependency from `host_env` crate to main workspace

* Declare dependency as workspace = true
2026-04-29 18:14:51 +09:00
Changjoon
330b18f2fe Preserve recursively-set value in defaultdict.__missing__ (#7718)
CPython's defaultdict.__missing__ (Modules/_collectionsmodule.c::defdict_missing)
calls default_factory() first; if the factory's recursion already populated
self[key] while running, the existing value is preserved instead of being
overwritten.

RustPython ships a Python fallback at Lib/collections/_defaultdict.py
(the C _collections.defaultdict is not available). That fallback
unconditionally executed self[key] = val after the factory returned,
overwriting any value the recursive call had already stored.

Add a 'if key in self: return self[key]' guard before the assignment.
dict.__contains__ does not invoke __missing__, so there's no recursion
risk; in the common non-reentrant case the check is False and behavior
is unchanged.

Unmasks test_defaultdict.TestDefaultDict.test_factory_conflict_with_set_value.
2026-04-29 18:14:32 +09:00
dependabot[bot]
82e8b200db Bump the unix group across 1 directory with 2 updates (#7713)
Bumps the unix group with 2 updates in the / directory: [nix](https://github.com/nix-rust/nix) and [rustyline](https://github.com/kkawakam/rustyline).


Updates `nix` from 0.30.1 to 0.31.2
- [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/compare/v0.30.1...v0.31.2)

Updates `rustyline` from 17.0.2 to 18.0.0
- [Release notes](https://github.com/kkawakam/rustyline/releases)
- [Changelog](https://github.com/kkawakam/rustyline/blob/master/History.md)
- [Commits](https://github.com/kkawakam/rustyline/compare/v17.0.2...v18.0.0)

---
updated-dependencies:
- dependency-name: nix
  dependency-version: 0.31.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: unix
- dependency-name: rustyline
  dependency-version: 18.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: unix
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-29 18:14:09 +09:00
Shahar Naveh
3f718f9942 Align _opcode.has_* functions with 3.14.4 (#7711)
* Align `_opcode.has*` with 3.14.4

* Unmark passing tests

* Unmark more passing tests
2026-04-28 12:15:23 +00:00
fanninpm
363d19839f Move dependencies to workspace for derive-impl crate (#7710)
* Add dependencies from `derive-impl` crate to main workspace

* Declare dependencies as workspace = true
2026-04-28 21:12:31 +09:00
Changjoon
68aece59c9 Reject format-string field index above Py_ssize_t::MAX (#7708)
CPython rejects digit-only format-string field names that overflow
Py_ssize_t at parse time with ValueError: Too many decimal digits in
format string (Python/string_parser.c::get_integer). RustPython's
FieldName::parse accepted any digit string usize::from_str could parse,
producing IndexError or KeyError at lookup instead.

Cap the parsed index at isize::MAX (Py_ssize_t::MAX on every platform)
inside FieldName::parse. Also reject digits-only strings whose value
overflows usize itself (caught when parse_usize returns None on an
all-digit input). A new FormatParseError::TooManyDecimalDigits maps to
the byte-identical CPython wording.

Unmasks test_str.StrTest.test_format_huge_item_number.
2026-04-28 21:12:09 +09:00
Changjoon
b3d6d2f247 Reject format spec with width above i32::MAX (#7707)
CPython rejects format-spec widths that exceed Py_ssize_t::MAX with
ValueError: Too many decimal digits in format string. RustPython's
FormatSpec::_parse only capped precision (via parse_precision); width
was accepted up to usize::MAX, so values like sys.maxsize + 1 silently
produced an effectively-ignored width.

Reject any width above i32::MAX with FormatSpecError::DecimalDigitsTooMany,
matching the existing precision cap and producing the byte-identical
ValueError wording.

Unmasks test_str.StrTest.test_format_huge_width.
2026-04-28 21:11:33 +09:00
dependabot[bot]
e6d9ea6bfe Bump insta from 1.46.3 to 1.47.2 (#7706)
Bumps [insta](https://github.com/mitsuhiko/insta) from 1.46.3 to 1.47.2.
- [Release notes](https://github.com/mitsuhiko/insta/releases)
- [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mitsuhiko/insta/compare/1.46.3...1.47.2)

---
updated-dependencies:
- dependency-name: insta
  dependency-version: 1.47.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-28 21:11:08 +09:00
dependabot[bot]
59382f385a Bump rustls from 0.23.38 to 0.23.39 (#7705)
Bumps [rustls](https://github.com/rustls/rustls) from 0.23.38 to 0.23.39.
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.23.38...v/0.23.39)

---
updated-dependencies:
- dependency-name: rustls
  dependency-version: 0.23.39
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-28 21:11:00 +09:00
dependabot[bot]
9db00f741c Bump the wasmtime group across 1 directory with 3 updates (#7704)
Bumps the wasmtime group with 3 updates in the / directory: [cranelift](https://github.com/bytecodealliance/wasmtime), [cranelift-jit](https://github.com/bytecodealliance/wasmtime) and [cranelift-module](https://github.com/bytecodealliance/wasmtime).


Updates `cranelift` from 0.130.1 to 0.131.0
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)

Updates `cranelift-jit` from 0.130.1 to 0.131.0
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)

Updates `cranelift-module` from 0.130.1 to 0.131.0
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)

---
updated-dependencies:
- dependency-name: cranelift
  dependency-version: 0.131.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: wasmtime
- dependency-name: cranelift-jit
  dependency-version: 0.131.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: wasmtime
- dependency-name: cranelift-module
  dependency-version: 0.131.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: wasmtime
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-28 21:10:52 +09:00
Changjoon
e5f2d2d3b9 Drop int() delegation to __trunc__ (#7703)
CPython 3.14 fully removed the long-deprecated fallback from int() to
__trunc__. Classes that want int(x) support must implement __int__ or
__index__; math.trunc(x) continues to use __trunc__.

Delete the __trunc__ branch in PyObject::try_int so a class with only
__trunc__ now reaches the existing TypeError arm, matching CPython.

Unmasks test_int.IntTestCases.test_intconversion.
2026-04-28 21:10:42 +09:00
Shahar Naveh
7fb743b1be Update traceback.py from 3.14.4 (#7702) 2026-04-28 21:10:33 +09:00
Changjoon
6c498fc4a7 Preserve str subclass type returned by __str__ / __repr__ (#7701)
Closes #7450.

CPython's unicode_new_impl returns the PyObject_Str result as-is when
type == &PyUnicode_Type, only invoking unicode_subtype_new for actual
str subclasses. RustPython's PyStr::Constructor stripped the result via
Self::from(s.as_wtf8().to_owned()) and re-materialized through
into_ref_with_type, dropping the subclass type even when cls is exactly
str.

Add a slot_new branch that returns input.str(vm)? directly when cls is
str_type with no encoding. Subtype construction and the bytes-decoding
path are unchanged.

Unmasks test_str.StrTest.test_conversion (11 assertTypedEqual cases).
2026-04-28 21:09:49 +09:00
Changjoon
b8f7ae4265 Reject __abc_tpflags__ with both SEQUENCE and MAPPING bits (#7700)
CPython's _abc._abc_init validates that __abc_tpflags__ doesn't combine
Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING (Modules/_abc.c::_abc_init).
RustPython's check_abc_tpflags copied the bits onto slots.flags without
that validation, so a class declaring both produced an inconsistent
type silently.

Validate the masked __abc_tpflags__ value against COLLECTION_FLAGS and
return a String error (which the existing new_heap_inner caller turns
into a TypeError) when both bits are present. Move the inheritance-skip
check after the self-attrs branch so a child overriding __abc_tpflags__
is still validated even when the parent already set one of the bits.
2026-04-28 21:09:16 +09:00
Changjoon
1d42ee565f Preserve __dict__ and __slots__ state in deque.__reduce__ (#7699)
deque.__reduce__ passed None as the unpickle state, so a deque
subclass's __dict__ and __slots__ values were dropped across a pickle
round-trip. CPython's deque___reduce___impl
(Modules/_collectionsmodule.c::deque___reduce___impl) calls
_PyObject_GetState, which returns the dict (or a (dict, slots) tuple)
so subclass attributes survive.

Replace the placeholder with the result of __getstate__() on the
instance. object.__getstate__ already implements the dict / dict+slots
protocol, matching _PyObject_GetState.
2026-04-27 12:50:23 +00:00
Changjoon
9794ab7fdf Enforce int_max_str_digits on int-to-str conversions (#7688)
* Enforce int_max_str_digits on int-to-str conversions

The str-to-int direction already enforced sys.get_int_max_str_digits()
via bytes_to_int; the int-to-str direction did not. CPython 3.14 enforces
both per PEP 644.

Adds check_int_to_str_digits helper in builtins::int (bit-count fast path
+ digit upper-bound from log10(2)), wired into the four Python-level
entry points: repr, the str fast path in protocol::object, int.__format__
(decimal/n/empty spec only — binary bases x/o/b are exempt per CPython),
and the DecimalD/I/U branches of vm::cformat for both str % and bytes %.

Unmasks 8 expectedFailure tests across test_int (max_str_digits, DoS
prevention, int_from_other_bases — each mirrored in IntSubclass),
test_ast (test_repr_large_input_crash) and test_reprlib (test_numbers).
Boundary cases (4299/4300/4301 digits at limit=4300) match CPython 3.14.4.

* Skip int-to-str DoS test on platforms without time.get_clock_info

The test_denial_of_service_prevented_int_to_str regression test uses
support.Stopwatch, which calls time.get_clock_info('monotonic'). In
RustPython that function is gated to unix/windows targets only, so on
wasm32-wasip1 it surfaces as AttributeError and breaks the wasm-wasi CI.
Guard the test with skipUnless(hasattr(time, 'get_clock_info'), ...) so
it runs everywhere it can and is skipped on wasm.

Also narrow is_decimal_int_format to Number(Case::Lower): 'N' is rejected
by format_int as UnknownFormatCode, so excluding it preserves that error
path instead of intercepting it with the digit-limit check.

* Add TODO: RUSTPYTHON marker to skipUnless reason

scripts/update_lib uses TODO: RUSTPYTHON markers inside unittest
decorator reason strings to identify and migrate custom RustPython
patches across CPython library updates.

* Use expectedFailureIf for wasm get_clock_info gap

skipUnless silently hides the test forever; expectedFailureIf surfaces
unexpected success once RustPython implements time.get_clock_info on
wasm, prompting marker removal.
2026-04-27 21:41:40 +09:00
Changjoon
dc81c740cf Match CPython wording for __slots__ conflict and __doc__ delete errors (#7698)
The behavior already matched CPython (the slot conflict is detected,
the __doc__ delete is rejected); only the message text drifted.

- "__slots__ conflicts with a class variable" -> drop the stray "a"
  to match CPython's "conflicts with class variable".
- "cannot delete '__doc__' attribute of type 'X'" -> insert "immutable"
  before "type" to match CPython's wording (CPython surfaces the same
  phrase even for user-defined classes since the descriptor refuses
  the delete unconditionally).
2026-04-27 21:39:21 +09:00
Changjoon
f10f441854 Defer staticmethod/classmethod callable storage to __init__ (#7697)
CPython's staticmethod and classmethod set __func__ and copy wrapper
attributes (__doc__, __name__, etc.) only inside __init__
(Objects/funcobject.c::sm_init / cm_init). RustPython did this work in
slot_new and again in __init__, so subclasses that override __init__
without calling super().__init__() saw __func__ pointing at the
original callable instead of None.

Move the callable assignment and the wrapper-attribute copy into
Initializer::init; slot_new now just validates the signature and stores
None for the callable, matching the CPython contract.
2026-04-27 21:38:44 +09:00
Shahar Naveh
1fa676fd07 Upgrade cspell to v10.0.0 (#7696)
* Update cspell to `v10.0.0`

* Force node version to be 24

* Ensure node24

* Disable cache
2026-04-27 21:37:55 +09:00
Shahar Naveh
5648a3346f Upgrade der to 0.8 (#7695)
* Update `der` to 0.8, move to workspace dependencies

* Add `pem` feature
2026-04-27 21:37:42 +09:00
dependabot[bot]
02c454bdb4 Bump libc from 0.2.185 to 0.2.186 (#7694)
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.185 to 0.2.186.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.186/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.185...0.2.186)

---
updated-dependencies:
- dependency-name: libc
  dependency-version: 0.2.186
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-27 12:38:06 +09:00
dependabot[bot]
049d44b1e0 Bump uuid from 1.23.0 to 1.23.1 (#7692)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.23.0 to 1.23.1.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.0...v1.23.1)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 1.23.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-27 12:37:57 +09:00
dependabot[bot]
f6b6b18b62 Bump foreign-types-shared from 0.1.1 to 0.3.1 (#7691)
Bumps [foreign-types-shared](https://github.com/sfackler/foreign-types) from 0.1.1 to 0.3.1.
- [Release notes](https://github.com/sfackler/foreign-types/releases)
- [Commits](https://github.com/sfackler/foreign-types/commits/foreign-types-shared-v0.3.1)

---
updated-dependencies:
- dependency-name: foreign-types-shared
  dependency-version: 0.3.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-27 12:37:48 +09:00
fanninpm
7f8cdddbbf Add crates/* directories to Dependabot config (#7687) 2026-04-27 10:15:14 +09:00
Changjoon
625e5bf012 Report invalid \uXXXX escape position at the u character (#7676)
* Report invalid \uXXXX escape position at the u character

CPython's json decoder reports the position of the `u` specifier
when a \uXXXX escape fails to parse, but RustPython was reporting
the preceding `\`. For surrogate-pair cases (\uXXXX\uYYYY) the
second call was passing char_offset + next_char_i + 1, which
lands on the first hex digit of the first escape -- unrelated to
the actual failure site.

Pass next_char_i (position of the primary `u`) to the primary
decode_unicode call, and capture the second `u`'s char index from
the next_tuple peek to pass to the surrogate-pair decode_unicode
call.

Verified: 13 targeted probes across invalid-hex, short, and pair
cases now all match CPython positions. test.test_json 214 tests
pass with no regressions.

* Add regression test for invalid \uXXXX escape position

* Use raise AssertionError instead of assert False (B011)
2026-04-26 12:33:34 +09:00
Shahar Naveh
a2afaf0f13 Pin actions version to commit; force runner to use node 24 (#7685)
* Pin actions version to commit; force runner to use node 24

* Pin more
2026-04-26 12:32:37 +09:00
dependabot[bot]
956267c49e Bump aws-lc-rs from 1.16.2 to 1.16.3 (#7684)
Bumps [aws-lc-rs](https://github.com/aws/aws-lc-rs) from 1.16.2 to 1.16.3.
- [Release notes](https://github.com/aws/aws-lc-rs/releases)
- [Commits](https://github.com/aws/aws-lc-rs/compare/v1.16.2...v1.16.3)

---
updated-dependencies:
- dependency-name: aws-lc-rs
  dependency-version: 1.16.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-26 12:32:10 +09:00
Changjoon
be43bb6dbf Match CPython error type for non-ASCII struct format arguments (#7681)
* Match CPython error type for non-ASCII struct format arguments

Struct() raised the wrong exception type when the format argument
contained non-ASCII characters:
- str input with non-ASCII char: RustPython raised UnicodeDecodeError
  with an empty message; CPython raises UnicodeEncodeError as if
  format.encode('ascii') had been called directly.
- bytes input with non-ASCII byte: same wrong UnicodeDecodeError;
  CPython passes the bytes through to the format parser, which then
  errors with struct.error("bad char in struct format").

Restructure IntoStructFormatBytes::try_from_object to:
- raise UnicodeEncodeError("ascii", s, start, start+1, "ordinal not
  in range(128)") for non-ASCII str, with start computed as the
  first non-ASCII code point position (matching CPython's natural
  encoding-error format);
- raise struct.error("bad char in struct format") for non-ASCII bytes,
  produced via the existing new_struct_error helper.

Probed byte-identical with CPython 3.14.4 for both cases. Full
test.test_struct (43 tests) passes with no regressions. Sanity-tested
all standard format/pack/unpack/calcsize call shapes remain unchanged.

* Add regression test for non-ASCII format string error types

* Use raise AssertionError instead of assert False (B011)
2026-04-26 12:31:54 +09:00
dependabot[bot]
6ab1f806ba Bump actions/cache from 5.0.4 to 5.0.5 (#7649)
Bumps [actions/cache](https://github.com/actions/cache) from 5.0.4 to 5.0.5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](668228422a...27d5ce7f10)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-25 21:57:22 +09:00
Shahar Naveh
f0e23aacc2 Opcode descs not hardcoded (#7682)
* Add `::iterator()` for oparg enums

* adjust `get_special_method_names`

* Adjust intristic function 2 oparg

* Adjust intristic function 1 oparg

* Adjust binary operator oparg
2026-04-25 21:57:02 +09:00
fanninpm
a5f48eaaa1 Add sha1 to crypto dependency group (#7679) 2026-04-25 21:54:01 +09:00
Changjoon
b427f31164 Raise ValueError for None in required AST scalar fields (#7680)
Match CPython's "field 'X' is required for Y" error format when
required scalar AST fields receive None during ast_from_object
conversion. Previously these produced TypeError or a generic
"None disallowed in expression list" ValueError.

Fields covered (Alias.name, Arg.arg, Comprehension.target/iter,
Keyword.value, MatchCase.pattern, WithItem.context_expr,
YieldFrom.value): all now raise the CPython-compatible message
verbatim.

Add a get_node_field_required helper alongside the existing
get_node_field / get_node_field_opt and switch the eight call
sites that read these required scalar fields. The helper rejects
None with the proper ValueError before the value flows into the
type converter where Rust's strong typing would otherwise force a
generic catch-all error. Optional fields (read via
get_node_field_opt) and fields where None is valid (e.g.
Constant.value) are unaffected.

This complements the existing AST validator pass at
crates/vm/src/stdlib/_ast/validate.rs (hooked at _ast.rs:763):
the validator handles post-conversion semantic invariants
(ExprContext, empty body, cross-field rules) but cannot handle
required-scalar-field None because Rust's non-Option types reject
None at conversion time, before validation runs.

Verified byte-identical with CPython 3.14.4 for all seven probe
cases. test.test_ast.test_ast: 227 tests, expected failures
38 -> 36 (test_empty_yield_from and test_none_checks unmasked,
no regressions). test.test_compile: 0 regressions.
2026-04-25 21:53:37 +09:00
dependabot[bot]
7df0801db3 Bump rustls from 0.23.37 to 0.23.38 (#7678)
Bumps [rustls](https://github.com/rustls/rustls) from 0.23.37 to 0.23.38.
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.23.37...v/0.23.38)

---
updated-dependencies:
- dependency-name: rustls
  dependency-version: 0.23.38
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-25 21:53:11 +09:00
dependabot[bot]
3a793ce716 Bump postcss from 8.5.3 to 8.5.10 in /wasm/demo (#7677)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.3 to 8.5.10.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.3...8.5.10)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.10
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-25 21:52:59 +09:00
dependabot[bot]
adafaf222b Bump github/gh-aw from 0.62.5 to 0.68.3 (#7650)
* Bump github/gh-aw from 0.62.5 to 0.68.3

Bumps [github/gh-aw](https://github.com/github/gh-aw) from 0.62.5 to 0.68.3.
- [Release notes](https://github.com/github/gh-aw/releases)
- [Changelog](https://github.com/github/gh-aw/blob/main/CHANGELOG.md)
- [Commits](48d8fdfddc...ce1794953e)

---
updated-dependencies:
- dependency-name: github/gh-aw
  dependency-version: 0.68.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix actionlint SC2129: use single group redirect block in upgrade-pylib.lock.yml

Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/73a3a050-ff0b-45ad-b5fd-fcadec36239a

Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
2026-04-25 05:17:05 +09:00
dependabot[bot]
f6371de4a1 Bump rustls-webpki from 0.103.10 to 0.103.13 (#7674)
Bumps [rustls-webpki](https://github.com/rustls/webpki) from 0.103.10 to 0.103.13.
- [Release notes](https://github.com/rustls/webpki/releases)
- [Commits](https://github.com/rustls/webpki/compare/v/0.103.10...v/0.103.13)

---
updated-dependencies:
- dependency-name: rustls-webpki
  dependency-version: 0.103.13
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-25 05:16:34 +09:00
Changjoon
fb1218d6ba Accept surrogates in _json.JsonScanner decode path (#7675)
The _json decoder had two failure modes when a Python str value would
contain a lone surrogate (legal per the Python 3 str model):

1. Boundary UnicodeEncodeError: JsonScanner::Callable::call rejected
   any input str with surrogates via try_into_utf8 before scanning
   began.
2. Silent U+FFFD corruption: call_scan_once and parse_object's key
   path called .to_string() on scanstring's Wtf8Buf output, which
   routes through Wtf8::Display (lossy). Array values and dict keys
   decoded from JSON \uXXXX escapes silently became U+FFFD.

Switch JsonScanner's five PyUtf8StrRef signatures to PyStrRef, drop
the entry-point try_into_utf8 call, and feed Wtf8Buf directly to
new_str instead of going through .to_string(). Key memoization now
uses HashMap<Wtf8Buf, PyStrRef> so surrogate-bearing keys survive
interning. parse_number takes &[u8] since JSON numbers are ASCII.

Extends the WTF-8 refactor pattern established in #7673 to the
decoder. machinery::scanstring already returns Wtf8Buf and is
unchanged.

Unmasks test_single_surrogate_decode. 214 tests in test.test_json
pass with no regressions. Decoder output verified byte-identical to
CPython 3.13.4 over 10,000 random fuzz cases (JSON docs containing
random surrogate escapes at root/list/dict positions, compared via
json.dumps(..., ensure_ascii=True, sort_keys=True)).
2026-04-25 05:16:12 +09:00
dependabot[bot]
3f8a0b12eb Bump actions/github-script from 8.0.0 to 9.0.0 (#7661)
* Bump actions/github-script from 8.0.0 to 9.0.0

Bumps [actions/github-script](https://github.com/actions/github-script) from 8.0.0 to 9.0.0.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](ed597411d8...3a2844b7e9)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: 9.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix SC2129 shellcheck warning: group consecutive >> redirects into single block

Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/dab79065-db94-41af-abb3-332d35557b29

Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
2026-04-25 05:15:31 +09:00
Changjoon
2e5c2be7fa Accept surrogates in _json.encode_basestring{,_ascii} (#7673)
encode_basestring/encode_basestring_ascii took PyUtf8StrRef, so
json.dumps(str_with_lone_surrogate) raised UnicodeEncodeError at the
Python/Rust boundary before write_json_string ran. CPython's encoder
emits \uXXXX under ensure_ascii=True and passes raw WTF-8 otherwise.

Switch to PyStrRef + s.as_wtf8(), matching scanstring in the same file.
Rewrite write_json_string to accept &Wtf8 and iterate
code_point_indices, emitting \uXXXX for surrogates in ascii mode and
passing their bytes through otherwise. Stop escaping 0x7F in the
ensure_ascii=False path (matches py_encode_basestring). Return Wtf8Buf
via the checked from_bytes so invariant breaks panic instead of UB.

Fuzzing also exposed two pre-existing ESCAPE_CHARS typos: 0x0B was
"\u000" and 0x1B was "\u001" (both missing trailing 'b'). Fixed here.

Verified byte-identical with CPython 3.13.4 over 16 manual + 10,000
random fuzz cases. Full test.test_json: 214 tests, 0 failures, 0
unexpected successes. Unmasks test_ascii_non_printable_encode and
test_single_surrogate_encode. Decoder path is a follow-up.
2026-04-25 00:08:14 +09:00
Shahar Naveh
0d67fd69e2 Relax rustyline version constraint (#7672) 2026-04-25 00:07:17 +09:00
Changjoon
952be48944 Unmask test_strftime_special and test_zones in datetimetester (#7671)
All three @unittest.skip("TODO: RUSTPYTHON") markers in
datetimetester.py are stale — the tests pass as written on current
main. Probed every assertion in each test body against both CPython
3.13.4 and RustPython: outputs are byte-identical for all unicode
passthrough cases (emoji, lone surrogates, surrogate pairs, null
bytes) in test_strftime_special and for all 11 FixedOffset
comparisons (utcoffset, tzname, dst, hash equality) in test_zones.

Full test.test_datetime run after unmasking: 1803 tests, 0 failures,
0 unexpected successes, 463 skipped (was 469 — 6 test-class inherited
runs now execute successfully).

Part of the skip-test inventory in #7611.
2026-04-25 00:07:06 +09:00
Changjoon
0913563bbe Raise ValueError for None in Stmt lists to match CPython validator (#7670)
Sibling fix to #7656 (which handled Vec<Expr>). The same catch-all
TypeError ("expected some sort of stmt, but got {}") in
Stmt::ast_from_object silently swallowed None, so compile() on an
AST with None in a statement-list field (ClassDef.body, Try.body,
For.body, etc.) raised TypeError where CPython raises
ValueError("None disallowed in statement list").

Add an is_none check before the catch-all, matching the Expr-side
shape introduced in #7656. Option<Stmt> positions are unaffected —
they short-circuit None earlier in node.rs.

Unmasks test_classdef (body=[None] sub-case). Full
test.test_ast.test_ast run: 227 tests, 0 unexpected successes, 38
expected failures (was 39).
2026-04-25 00:06:53 +09:00
Changjoon
1ab76d012b Fix ShellCheck findings in lib-deps-check and update-doc-db workflows (#7669)
Clear four of the five ShellCheck findings reported in #7653. The
fifth finding is in upgrade-pylib.lock.yml — a gh-aw-generated file
marked "DO NOT EDIT" whose source .md does not contain the flagged
pattern — and is out of scope here; it will resolve via the in-flight
gh-aw version bump (#7650).

lib-deps-check.yaml:
  * SC2076: replace `=~ " $module "` (quoted regex interpreted
    literally) with `== *" $module "*` glob match. Same intent
    (literal substring), same semantics across regex metachars.
  * SC2086: quote `$GITHUB_OUTPUT` in the output redirect.

update-doc-db.yml:
  * SC2129: collapse the eight sequential `echo ... >> $OUTPUT_FILE`
    lines (plus one `cat ... >> $OUTPUT_FILE`) into a single grouped
    redirect `{ ...; } > "$OUTPUT_FILE"`. Drops the now-redundant
    `echo -n '' > $OUTPUT_FILE` truncate.
  * SC2016: add `# shellcheck disable=SC2016` above the block; the
    backticks in the auto-generated-header comment are literal
    Markdown, not command substitution.

Verified locally with shellcheck 0.11.0: both modified blocks
produce no ShellCheck output. Semantic equivalence of the
lib-deps-check change confirmed across six test inputs including
regex metachars and glob-meaningful characters.
2026-04-25 00:06:23 +09:00
dependabot[bot]
f0acc67855 Bump https://github.com/astral-sh/ruff-pre-commit (#7668)
Bumps [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit) from v0.15.7 to 0.15.11.
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.7...v0.15.11)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.15.11
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-25 00:05:24 +09:00
dependabot[bot]
9ebdf10c11 Bump https://github.com/rbubley/mirrors-prettier from v3.8.1 to 3.8.3 (#7667)
Bumps [https://github.com/rbubley/mirrors-prettier](https://github.com/rbubley/mirrors-prettier) from v3.8.1 to 3.8.3.
- [Commits](https://github.com/rbubley/mirrors-prettier/compare/v3.8.1...v3.8.3)

---
updated-dependencies:
- dependency-name: https://github.com/rbubley/mirrors-prettier
  dependency-version: 3.8.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-25 00:05:06 +09:00
Roman
7bb2fb0755 Fix pyclass memory layout to prevent silent UB in inherited getter dispatch (#7663) 2026-04-24 23:30:23 +09:00
dependabot[bot]
43ef2eabbe Bump indexmap from 2.13.0 to 2.14.0 (#7666) 2026-04-24 07:54:37 -04:00
Joshua Megnauth
dc0c814671 Match CPython's islower/isupper exactly (#7646)
This PR fixes a regression from my last islower/isupper patch.
Python's Bytes doesn't assume an encoding, so methods like islower
should only consider ASCII casing.

I updated islower/isupper for UTF-8 and WTF-8 to match CPython more
closely. The two functions now use the same properties as CPython and
now match CPython exactly.

I updated the unit tests to pass on Python 3.15. Unicode updates
sometimes cause properties to shift. I previously tested everything on
Python 3.14, but that lead to failures that I assumed were bugs but were
actually due to Unicode differences. For example, U+0295 is a lower case
letter in older Unicode versions but is NOT in newer versions.

One of the new tests is disabled on Python 3.14 for now because it will
fail in CI till CI is bumped to 3.15.
2026-04-24 13:30:02 +09:00
fanninpm
a7ea01a135 Change dependency groups (#7665)
* Add `ahash` to `random` group

* Add `unix` group with `nix`, `mac_address`, and `rustyline`
2026-04-24 13:29:07 +09:00
dependabot[bot]
cbfb313de2 Bump rustls-platform-verifier from 0.6.2 to 0.7.0 (#7664)
Bumps [rustls-platform-verifier](https://github.com/rustls/rustls-platform-verifier) from 0.6.2 to 0.7.0.
- [Release notes](https://github.com/rustls/rustls-platform-verifier/releases)
- [Changelog](https://github.com/rustls/rustls-platform-verifier/blob/main/CHANGELOG)
- [Commits](https://github.com/rustls/rustls-platform-verifier/compare/v/0.6.2...v/0.7.0)

---
updated-dependencies:
- dependency-name: rustls-platform-verifier
  dependency-version: 0.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-24 09:10:30 +09:00
dependabot[bot]
adb169e65b Bump webpki-roots from 1.0.6 to 1.0.7 in the webpki-root group (#7662)
Bumps the webpki-root group with 1 update: [webpki-roots](https://github.com/rustls/webpki-roots).


Updates `webpki-roots` from 1.0.6 to 1.0.7
- [Release notes](https://github.com/rustls/webpki-roots/releases)
- [Commits](https://github.com/rustls/webpki-roots/compare/v/1.0.6...v/1.0.7)

---
updated-dependencies:
- dependency-name: webpki-roots
  dependency-version: 1.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: webpki-root
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-24 07:51:38 +09:00
dependabot[bot]
5081f76faf Bump openssl from 0.10.77 to 0.10.78 (#7659)
Bumps [openssl](https://github.com/rust-openssl/rust-openssl) from 0.10.77 to 0.10.78.
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-v0.10.77...openssl-v0.10.78)

---
updated-dependencies:
- dependency-name: openssl
  dependency-version: 0.10.78
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-24 02:01:17 +09:00
Changjoon
f2e055f7d6 Raise ValueError for None in Expr lists to match CPython validator (#7656)
* Raise ValueError for None in Expr lists to match CPython validator

CPython raises ValueError("None disallowed in expression list") when
compile() encounters None as an element in a Vec<Expr> position
(Set/List/Tuple.elts, BoolOp.values, Call.args, comprehension generators,
etc.). RustPython reached the catch-all TypeError("expected some sort of
expr, but got {}") branch instead, failing a dozen test_ast validator
tests.

Add an explicit is_none() check in Expr::ast_from_object before the
catch-all so None short-circuits with the CPython-compatible ValueError.
Option<Expr> positions (e.g. Dict.keys for **unpack) are unaffected
since node.rs Option<T>::ast_from_object handles None earlier.

Unmasks 12 skip / expectedFailure markers in
Lib/test/test_ast/test_ast.py:
- test_boolop, test_set, test_list, test_tuple
- test_call, test_delete, test_assign
- test_dict, test_dictcomp, test_generatorexp, test_listcomp, test_setcomp

Addresses part of the skip-test inventory in #7611.

The Stmt-level analog (body=[None] -> "None disallowed in statement list",
used by test_classdef) needs a sibling change in statement.rs; out of
scope here.

* Drop redundant .to_owned() on new_value_error argument

Matches the other new_value_error call sites in the file.

Co-authored-by: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com>

---------

Co-authored-by: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com>
2026-04-23 15:32:33 +09:00
Shahar Naveh
f2f20175b3 oparg_enum! to support custom display value (#7654) 2026-04-23 15:32:01 +09:00
dependabot[bot]
a693a0c8aa Bump uuid from 1.22.0 to 1.23.0 (#7565)
* Bump uuid from 1.22.0 to 1.23.0

Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.22.0 to 1.23.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.22.0...v1.23.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 1.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix CI for uuid 1.23.0 Context deprecation

Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/bec0d567-f41e-407f-bf99-0463ca555f74

Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
2026-04-23 15:26:19 +09:00
Changjoon
71380bead9 Fix process abort on large float format precision (#7633)
* Fix process abort on large float format precision

Formatting a float with large precision (>= ~65535) aborted the
interpreter instead of raising a Python exception. CPython handles
the same input by returning a clean string.

  # Before
  ./rustpython -c "print(f'{1.5:.1000000}')"
  thread 'main' panicked at crates/literal/src/float.rs:135:
  Formatting argument out of range   (exit 101, abort)

  # After
  ./rustpython -c "print(f'{1.5:.1000000}')"
  1.5

Root cause: Rust's `format!("{:.*}", n, x)` panics when `n`
exceeds the fmt runtime's internal precision limit. `format_fixed`
already caps `n` at u16::MAX, but `format_general` and
`format_exponent` (and the `%` branch in `crates/common/src/format.rs`)
passed user-supplied precision straight through to `format!`.

Fix:

  * Introduce `FMT_MAX_PRECISION` + `clamp_fmt_precision()` in
    crates/literal/src/float.rs. Cap is `u16::MAX - 1` because
    `{:.*e}` hits a second panic (`ndigits > 0` in core flt2dec)
    at exactly u16::MAX; the smaller value covers both paths.
  * Apply the helper to `format_fixed` (replacing the existing
    ad-hoc cap), `format_exponent` (entry), and `format_general`
    (three separate format! calls with saturating arithmetic on
    derived precision values).
  * Apply the helper in the `FormatType::Percentage` branch in
    crates/common/src/format.rs.

This is harmless for all normal inputs — f64 carries only ~17
significant digits, so precision beyond 65K is padding zeros at
best. Complex-number and old-style `%`-formatting paths transitively
benefit because they dispatch to the same library functions.

Verified:

  * cargo run -- -m test test_float test_fstring test_format:
    144 passed, 0 regressed.
  * extra_tests/snippets/builtin_format.py: all assertions pass,
    including 7 new regression cases covering e / E / g / G / f /
    % at precision 1_000_000.
  * Probed with 10 magnitude values (0, ±1.5, ±inf, nan, 1e-300,
    1e300, f64::MAX, 5e-324) x 4 format types = 40 combinations,
    plus precision 0/1/2 boundary, complex formatting, old-style
    `%` formatting, and combined specs (fill/align/sign/grouping/
    zero-pad). All return clean strings; no process abort.

* Address CodeRabbit review: split cap + drop redundant clamp

Two refinements after CodeRabbit review:

1. Drop the redundant `format!("{:.*}", precision + 1, base)` in
   `format_general`'s scientific branch. It was a no-op pre-fix
   (magnitude is `.abs()`-ed at the caller, so `base` has no sign
   and its length was exactly `precision + 1`), but after I added
   the cap it turned into an active truncate — dropping 1 char of
   precision at the cap boundary. Reuse `base` directly and extract
   `exp_precision` for reuse by `decimal_point_or_empty`.

2. Split the cap into two helpers.

   `FMT_MAX_PRECISION = u16::MAX`           — for plain `{:.*}` (format_fixed,
                                                 %-branch, format_general's
                                                 non-scientific branch).
   `FMT_MAX_EXP_PRECISION = u16::MAX - 1`   — for `{:.*e}` (format_exponent,
                                                 format_general's scientific
                                                 entry).

   The second value is one lower because `{:.*e}` trips an additional
   `ndigits > 0` assertion in `core::num::flt2dec` at exactly
   `u16::MAX`. The first commit used the tighter cap uniformly,
   which silently regressed `format_fixed` by 1 char at
   `precision == u16::MAX` (it previously capped at exactly that
   value). Two helpers restore byte-identical CPython parity for
   fixed / percent / general-non-scientific paths up through
   `precision == u16::MAX`.

Verification:
  * precision 5 .. 65534:  360 outputs byte-identical to CPython
                           across 8 magnitudes x 9 precisions x 5 types.
  * precision == 65535:    f / g / G / % now match CPython (0 diff).
                           e / E remain 1 char shorter — unavoidable
                           within the `u16::MAX - 1` exp cap.
  * precision > 65535:     output stops at cap; CPython emits full
                           padding — same design divergence as before.
  * No panic regression:   f-string default, e/E, g/G, %, f at
                           precision 1_000_000 all return cleanly.
  * Test suite:            test_float + test_fstring + test_format,
                           162 passed, 0 regressed.

* Fix ruff format: single-line precision clamp

* Address @youknowone review: byte-identical CPython parity at boundary

Per review comment on `extra_tests/snippets/builtin_format.py:209`:
the patch declares `FMT_MAX_PRECISION = u16::MAX`, so the tests must
cover 65535 and 65536 and demonstrate CPython parity at the boundary.

The previous version only avoided panic — at the cap it silently
truncated 1 char short of CPython for e / E, and thousands of chars
short for f / %  at precision beyond the cap. This commit restores
byte-identical CPython output at every precision up to the format-
spec parser's own `i32::MAX` ceiling.

Fix: pad the Rust-format result with '0's up to the user-requested
precision.

Why this is correct, not a workaround: IEEE 754 double has at most
~767 significant decimal digits; past that, every digit is
deterministically '0' in both CPython and the native Rust output.
Our cap (65534 for exp, 65535 for plain) sits far above 767, so
appending zeros reconstructs precisely what CPython would have
produced. Verified on hard inputs: `1e-100`, `5e-324` (subnormal
boundary), `f64::MAX`, mixed magnitudes — the last 100 chars of
Rust-format output at precision 65534 are all '0' for every case.

Changes:

  * `format_fixed`: after format!(), extend with (precision - capped)
    '0' chars before appending the optional decimal point.
  * `format_exponent`: same, applied to the parsed mantissa before
    reassembling with the exponent marker.
  * `FormatType::Percentage` branch: same. Also fixed a bug the
    boundary audit surfaced: the finite-input overflow guard used
    `return Ok("inf%")`, which bypasses the outer sign handler.
    Changed to a match-arm value so `format_sign_and_align` still
    runs and produces "-inf%" for `-f64::MAX`, matching CPython.

Verification:

  * 7 magnitudes × 5 precisions × 6 format types = 210 comparisons
    against CPython at precisions {65534, 65535, 65536, 100000,
    200000}. All 210 byte-identical.
  * Gap audit (complex formatting, old-style % formatting, negative
    magnitudes, -0.0, combined specs with fill / sign / alternate /
    grouping) at boundary precisions. All but 20 byte-identical.
    The 20 remaining diffs all stem from a pre-existing
    complex-imaginary-part repr bug (`1e100j` expands to 100 '0's
    in RustPython vs CPython's `1e+100j`) which reproduces on
    upstream main without any part of this patch and is out of
    scope here.
  * `cargo run -- -m test test_float test_fstring test_format`:
    162 passed, 0 regressed.
  * `extra_tests/snippets/builtin_format.py` now pins exact
    expected strings at 65534 / 65535 / 65536 / 1_000_000 for
    every format type, plus the `f64::MAX × 100 → 'inf%'`
    overflow case.
  * `cargo fmt --check`: pass.

* Clarify boundary test labels + add past-cap depth assertions

Rename the boundary-test section so the three precision points per
format type are labeled below / at / past the cap inline, making the
"past MAX_PRECISION" unhappy-case coverage explicit. Add len-based
assertions at precision 1_000_000 for f, e, and % to exercise the
cap-then-pad path at a depth far beyond the boundary.
2026-04-23 15:25:52 +09:00
dependabot[bot]
5a45d41df0 Bump cranelift from 0.130.0 to 0.130.1 in the wasmtime group (#7652)
Bumps the wasmtime group with 1 update: [cranelift](https://github.com/bytecodealliance/wasmtime).


Updates `cranelift` from 0.130.0 to 0.130.1
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)

---
updated-dependencies:
- dependency-name: cranelift
  dependency-version: 0.130.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: wasmtime
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-22 08:09:28 +09:00
dependabot[bot]
9b0c668f74 Bump cargo-bins/cargo-binstall from 1.17.9 to 1.18.1 (#7651)
Bumps [cargo-bins/cargo-binstall](https://github.com/cargo-bins/cargo-binstall) from 1.17.9 to 1.18.1.
- [Release notes](https://github.com/cargo-bins/cargo-binstall/releases)
- [Changelog](https://github.com/cargo-bins/cargo-binstall/blob/main/release-plz.toml)
- [Commits](0b24824336...dc19f1e484)

---
updated-dependencies:
- dependency-name: cargo-bins/cargo-binstall
  dependency-version: 1.18.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-22 08:09:16 +09:00
Shahar Naveh
dc65255fd2 Use cfg_select! (#7636) 2026-04-21 17:35:26 +00:00
Pravin
a9fd4bf41f Bump rust version to 1.94.0 (#7442) 2026-04-22 00:01:09 +09:00
dependabot[bot]
5058090a3f Bump zizmorcore/zizmor-action from 0.5.2 to 0.5.3 (#7638)
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.5.2 to 0.5.3.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](71321a20a9...b1d7e1fb5d)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.5.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-21 23:59:54 +09:00
Jeong, YunWon
b929a50647 compiler parity for typealias (#7645) 2026-04-21 23:59:20 +09:00
dependabot[bot]
f8862e4eed Bump j178/prek-action from 2.0.1 to 2.0.2 (#7637)
Bumps [j178/prek-action](https://github.com/j178/prek-action) from 2.0.1 to 2.0.2.
- [Release notes](https://github.com/j178/prek-action/releases)
- [Commits](53276d8b0d...cbc2f23eb5)

---
updated-dependencies:
- dependency-name: j178/prek-action
  dependency-version: 2.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-21 22:32:27 +09:00
dependabot[bot]
18c6c16e2a Bump the openssl group with 2 updates (#7643)
Bumps the openssl group with 2 updates: [openssl](https://github.com/rust-openssl/rust-openssl) and [openssl-sys](https://github.com/rust-openssl/rust-openssl).


Updates `openssl` from 0.10.76 to 0.10.77
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-v0.10.76...openssl-v0.10.77)

Updates `openssl-sys` from 0.9.112 to 0.9.114
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-sys-v0.9.112...openssl-sys-v0.9.114)

---
updated-dependencies:
- dependency-name: openssl
  dependency-version: 0.10.77
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: openssl
- dependency-name: openssl-sys
  dependency-version: 0.9.114
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: openssl
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-21 22:32:11 +09:00
Copilot
d5921d16af Create rustpython-host-env (#7582)
* Create rustpython-host-env crate; move host abstractions out of common

Move os, crt_fd, fileutils, windows, macros modules from
rustpython-common into the new rustpython-host-env crate.
This isolates host OS API wrappers behind a crate boundary
with zero Python runtime dependency.

- Add crates/host_env to workspace
- Drop nix, windows-sys, widestring deps from common
- Wire vm and stdlib to depend on rustpython-host-env
- Migrate all imports from common::{os,crt_fd,fileutils,windows}
  to rustpython_host_env::

* refactor: extract host helpers

Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/48d1e64d-37ce-409f-b511-8e61a349665c

Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>

* lint: enforce direct host API boundaries

Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/97225fb7-7b3d-4197-a77c-eb44aead5b13

Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>

* refactor: extract remaining host env helpers

Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/d96f57e1-b196-4460-9983-97d5ff118835

Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>

* fix: clean extracted host env follow-up

Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/d96f57e1-b196-4460-9983-97d5ff118835

Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>

* fix: document env mutation safety

Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/d96f57e1-b196-4460-9983-97d5ff118835

Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>

* refactor: split host fs helpers from fileutils

Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/c57424c5-0e1d-490a-82b3-2d2f6c8cf2cd

Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>

* fix: resolve latest host env ci regressions

Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/899eb717-ebc6-4a4a-870c-2a15c5f33e02

Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>

* fix: resolve remaining windows clippy host fs calls

Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/12f32740-8173-4b10-a1d6-00b29e90a8ec

Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>

* host_env

---------

Co-authored-by: Jeong, YunWon <jeong@youknowone.org>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
2026-04-21 00:13:25 +09:00
Jeong, YunWon
9140ef583a Bytecode parity - boolop, comprehension, CFG passes (#7631)
* Bytecode parity - boolop, comprehension, CFG passes

- Flatten nested same-op BoolOp and add IfExpr to jump_if
- Simplify is_name_imported to module-level only
- Enable inlined comprehensions in module/class scope
- Add TweakInlinedComprehensionScopes with fast_hidden tracking
- Track fb_range in FBlockInfo for with-statement line info
- Add constant subscript folding and unary Not folding
- Add emit_return_const_no_location for implicit returns
- Use JumpNoInterrupt for ternary/except jumps
- Add STACK_USE_GUIDELINE threshold for collection building
- Reorder CFG pipeline: inline small blocks earlier, resolve
  line numbers before cold block extraction
- Add redirect_empty_unconditional_jump_targets,
  reorder_conditional_chain_and_jump_back_blocks,
  materialize_empty_conditional_exit_targets,
  duplicate_shared_jump_back_targets passes
- Add borrow deoptimization for multi-handler, named-except,
  protected conditional tail, and protected import joins
- Run folding/optimization passes twice around peephole

* Bytecode parity - slice augassign, async comp inline

- Augmented assignment with two-part slices uses BINARY_SLICE/STORE_SLICE
- Inline async comprehensions (remove await/async guards)
- Inlined comprehension cleanup jump uses JumpNoInterrupt
- Class firstlineno uses first decorator line when decorated
- Recurse into nested functions for static attribute collection
- Fold unary positive complex constants (+0.0j)
- Add deoptimize_borrow_for_folded_nonliteral_exprs pass
- Add inline_single_predecessor_artificial_expr_exit_blocks pass
- Skip shared artificial expr-stmt exit blocks in small-block inlining
- Mark folded boolop tail as folded_from_nonliteral_expr

* apply reviews
2026-04-21 00:10:00 +09:00
Changjoon
af41d11faf Fix complex repr to use scientific notation for large integer-valued components (#7634)
* Fix complex repr to use scientific notation for large integer-valued components

repr of a complex number whose real or imaginary part is an integer-valued
float with |x| >= 1e16 emitted the full decimal expansion instead of
scientific notation, diverging from CPython:

  Before (RustPython):
    repr(1e100 + 1e100j)
      (10000000000000000000000000000000000000000000000000000000000
       0000000000000000000000000000000000000000000+1000000000000000
       000000000000000000000000000000000000000000000000000000000000
       00000000000000000000000000000000000000j)

  After / CPython:
    (1e+100+1e+100j)

Root cause in crates/literal/src/complex.rs::to_string — it bifurcated
each component by .fract() == 0.0:

  if im.fract() == 0.0 { im.to_string() }       // Rust's default Display
  else                 { float::to_string(im) } // scientific for large/small

Rust's Display never uses scientific notation, so any integer-valued f64
(including 1e16, 1e17, 1e100 which are exactly representable as integers)
routed through the wrong branch and produced the full decimal expansion.
Non-integer magnitudes reached float::to_string and rendered correctly.

The fix is to use one helper per component that implements CPython's
actual PyOS_double_to_string(format='r') rule: scientific notation when
|x| < 1e-4 or |x| >= 1e16, otherwise Rust's default Display (which drops
the trailing '.0' for integer-valued floats — matching CPython's
(1+2j) convention rather than (1.0+2.0j)). The threshold matches
float::to_string; the only behavioral difference is that complex
components render 1.0 as "1" rather than "1.0".

Verified:
  * 29 CPython reference cases (normal / boundary / extremes / special /
    signed-zero) — all byte-identical after fix.
  * 18 additional edge cases (subnormal 5e-324, f64::MAX, MIN_POSITIVE,
    DBL_EPSILON, threshold-straddling values) — all byte-identical.
  * Lib/test/test_complex.py::test_repr_str /
    test_negative_zero_repr_str / test_repr_roundtrip — all pass.
  * cargo run -- -m test test_complex — 37 passed.
  * cargo run -- -m test test_float test_long — 101 passed.
  * ast.unparse() round-trip of source containing complex literals
    (e.g. 1e100 + 1e-100j, 1e17 + 1j) produces CPython-identical output.
  * extra_tests/snippets/builtin_complex.py — 20+ new regression cases.

* Address CodeRabbit review: clarify threshold boundary test comment

The comment claimed all three assertions stay in non-scientific form,
but the 1e-5 case explicitly verifies scientific notation (since
|1e-5| < 1e-4 falls outside the decimal-form range). Reworded the
header to describe the axis being tested (threshold boundary) and
added per-case inline notes indicating each assertion's expected
form.
2026-04-20 21:56:41 +09:00
Changjoon
175f12b664 Fix stack overflow on deeply-nested JSON in json.loads() (#7632)
* Fix stack overflow on deeply-nested JSON in json.loads()

json.loads() on a deeply-nested array or object payload (e.g.
'[' * 50000 + ']' * 50000) overflowed the native Rust stack and
crashed the interpreter process with SIGSEGV. CPython raises
RecursionError on the same input via _Py_EnterRecursiveCall in
Modules/_json.c.

The recursion lives in the mutual call chain:
  JsonScanner::parse_object / parse_array
    -> JsonScanner::call_scan_once
      -> JsonScanner::parse_object / parse_array

Every descent funnels through call_scan_once, so wrapping its body
with vm.with_recursion covers both '{' and '[' paths (and their
mixed nesting) with a single guard.

Before:
  ./rustpython -c "import json; json.loads('[' * 50000 + ']' * 50000)"
    -> SIGSEGV (exit 139)

After:
  -> RecursionError: maximum recursion depth exceeded while
     decoding a JSON object from a string

Verified:
  - extra_tests/snippets/stdlib_json.py: all assertions pass
    (includes 3 new regression cases: array, object, alternating
    nesting at depth 100000)
  - cargo run -- -m test test_json: 214 passed, 0 regressed
    (9 skipped, 13 expected failures, all pre-existing)
  - depth 500000 no longer crashes (RecursionError)
  - shallow parsing unchanged

* Enable test_highly_nested_objects_decoding

Per @ShaharNaveh's review on #7632: this test was previously marked
`@unittest.skip("TODO: RUSTPYTHON; crashes")` because json.loads
would SIGSEGV on the 500_000-deep input. The recursion-guard added
in this PR makes it raise RecursionError like CPython, so the skip
decorator can be removed.

  $ cargo run -- -m unittest \
        test.test_json.test_recursion.TestCRecursion.test_highly_nested_objects_decoding \
        test.test_json.test_recursion.TestPyRecursion.test_highly_nested_objects_decoding
  ...
  Ran 2 tests in 0.825s
  OK

  $ cargo run -- -m test test_json
  Ran 214 tests (7 skipped, 13 expected failures) — all pass.
2026-04-20 21:52:17 +09:00
Shahar Naveh
b18b71b2db Auto-retry flaky MP tests (#7603) 2026-04-19 10:37:22 -04:00
Changjoon
fdb49d83c5 Fix segfault on cyclic or deeply-nested AST in compile() (#7630) 2026-04-19 22:35:09 +09:00
Shahar Naveh
37707081f8 Update test_named_expressions.py from 3.14.4 (#7629) 2026-04-19 22:33:51 +09:00
Shahar Naveh
764e4de061 Update test_descr.py and test_decorators.py from 3.14.4 (#7628)
* Update `test_descr.py` and `test_decorators.py` from 3.14.4

* Mark failing tests

* Use correct test marker
2026-04-19 22:33:27 +09:00
Changjoon
b842a6c6c6 Fix struct_time field overflow to raise OverflowError in time module (#7627)
* Fix struct_time field overflow to raise OverflowError in time module

* Address CodeRabbit review: cover tm_gmtoff and chain AssertionError

* Fix ruff format: single space before inline comment
2026-04-19 22:32:57 +09:00
Joshua Megnauth
9669118d3c Use Unicode properties for alnum, alpha, etc. (#7626)
Rust and Python differ in which properties they use for alphanumeric,
numeric, et cetera. Both languages list which properties are used which
makes it easy to mimic Python's behavior in Rust.

My previous patch was a bit shortsighted because I filtered out
combining characters from is_alphanumeric. Using properties is exact and
also much cleaner. It also covers edge cases that my initial approach
missed.

Besides isalnum, I also fixed isnumeric and isdigit in the same way by
using properties.
2026-04-19 14:10:00 +09:00
Changjoon
67eedddcd7 Fix error messages in binary/ternary ops to match CPython format (#7625)
* Fix `divmod` error message to match CPython format

* Fix ternary op error message separator to match CPython
2026-04-19 09:15:12 +09:00
Changjoon
57ca1d59a6 Update test_base64.py from 3.14.4 (#7624) 2026-04-19 09:14:46 +09:00
Changjoon
9a0410dab4 Update test_cmath.py from 3.14.4 (#7623) 2026-04-19 09:14:35 +09:00
Jeong, YunWon
b80c2bd5ec Merge pull request #7622 from youknowone/bytecode-parity-string 2026-04-18 17:52:34 +09:00
Jeong, YunWon
a5b9f0e80b refactro 2026-04-18 17:20:08 +09:00
Jeong, YunWon
caf8d55da5 Bytecode parity - folding, class prologue, except cleanup
Constant folding:
- Add string/bytes multiply and bytes concat folding in IR
- Add constant subscript folding (str, bytes, tuple indexing)
- Delegate list/set constant folding to IR passes
- Stream big non-const list/set via BUILD+LIST_APPEND

Class/generic compilation:
- Reorder class body prologue: __type_params__ before __classdict__
- Build class function before .generic_base in generic classes
- Register .type_params/.generic_base symbols in proper scopes
- Use load_name/store_name helpers for synthetic variables

Return block handling:
- Only duplicate return-None epilogues, not arbitrary returns
- Add inline_pop_except_return_blocks pass
- Add duplicate_named_except_cleanup_returns pass

Other fixes:
- Fix eliminate_dead_stores to only collapse adjacent duplicates
- Skip STORE_FAST_LOAD_FAST superinstruction in generators after FOR_ITER
- Thread jumps through NOP-only blocks
- Transfer NOP line info to following unconditional jumps
- Extract scope_needs_conditional_annotations_cell helper
- Register __conditional_annotations__ for module future annotations
2026-04-18 15:17:56 +09:00
Jeong, YunWon
c79baa3317 Fix for-loop target NOP and t-string stack order
- Remove unnecessary NOP between FOR_ITER and unpack/store
  by compiling loop target directly on target range
- Fix t-string compilation to match stack order: build
  strings tuple first, then evaluate interpolations
- Split compile_tstring_into into collect_tstring_strings
  and compile_tstring_interpolations
- Handle debug text literals and default repr conversion
  for debug specifier in t-strings
- Always set bit 1 in BUILD_INTERPOLATION oparg encoding
2026-04-18 10:18:30 +09:00
Jeong, YunWon
f0bf8100c9 Inline with-suppress return blocks and extend return duplication
- Add inline_with_suppress_return_blocks pass to inline return
  epilogues after with-suppress cleanup sequences
- Extend duplicate_end_returns to handle conditional jumps to the
  final return block, not just unconditional ones
- Process jump targets in reverse order to preserve indices
- Add extra deoptimize_store_fast_store_fast pass after superinstructions
- Add tests for listcomp cleanup tail and with-suppress tail
2026-04-18 09:37:38 +09:00
Jeong, YunWon
1f1be5e29e Align bytecode codegen structure with CPython 3.14 (#7588)
* Align bytecode codegen structure with CPython 3.14

* Bytecode parity - constant folding, annotation ordering, superinstruction alignment

- Add BoolOp constant folding with short-circuit semantics in compile_expression
- Add constant truthiness evaluation for assert statement optimization
- Disable const collection/boolop folding in starred unpack and assignment contexts
- Move annotation block generation after body with AnnotationsPlaceholder splicing
- Reorder insert_superinstructions to run before push_cold_blocks (matching flowgraph.c)
- Lower LOAD_CLOSURE after superinstructions to avoid false LOAD_FAST_LOAD_FAST
- Add ToBool before PopJumpIf in comparisons and chained compare cleanup blocks
- Unify annotation dict building to always use incremental BuildMap + StoreSubscr
- Add TrueDivide constant folding for integer operands
- Fold constant sets to Frozenset (not Tuple) in try_fold_constant_collection
- Add PyVmBag for frozenset constant materialization in code objects
- Add remove_redundant_const_pop_top_pairs pass and peephole const+branch folding
- Emit Nop for skipped constant expressions and constant-true asserts
- Preserve comprehension local ordering by source-order bound name collection
- Simplify annotation scanning in symboltable (remove simple-name gate)

* Fix CI regressions in marshal and fast-local ops

* impl more

* Align bytecode codegen with CPython structure

* Bytecode parity - comprehension/except scope ordering, load_fast_borrow fixes

- Reorder comprehension symbol-table walk so the outermost iterator
  registers its sub_tables in the enclosing scope before the comp
  scope, and rescan elt/ifs in CPython's order. Codegen peeks past the
  outermost iterator's nested scopes to find the comprehension table.
- For plain try/except, emit handler sub_tables before the else block
  so codegen's linear sub_table cursor stays aligned.
- Rename `collect_simple_annotations` to `collect_annotations` and
  evaluate non-simple annotations during __annotate__ compilation to
  preserve source-order side effects while keeping the simple-name
  index stable.
- Dedupe equivalent code constants in `arg_constant` and add a
  structural equality check on `CodeObject`.
- Disable LOAD_FAST_BORROW for the tail end block when a try has a
  bare `except:` clause, and have `new_block` inherit the flag from
  the current block.
- Remove `cfg!(debug_assertions)` guard around the
  `optimize_load_fast_borrow` start-depth check so mismatches are
  handled (return instead of assert) in release builds.
- Collapse nop-only blocks that precede a return epilogue and hoist
  the prior line number into the next real instruction so the
  line table matches.
- Unmark now-passing `test_consts_in_conditionals`,
  `test_load_fast_unknown_simple`,
  `test_load_fast_known_because_already_loaded`, and PEP 646 f3/f4
  annotation checks.

* Bytecode parity - try/except line tracking, assert 0 shape

- In `compile_try_except`, drop the leading Nop and set the end
  block's source range from the last orelse/body statement so line
  events after the try fall on the right line.
- Recognise constant-false asserts as the direct-raise shape (no
  ToBool/PopJumpIfFalse) and flip the test assertion accordingly.
- Extend `remove_redundant_nops_in_blocks` to also look through a
  trailing nop before a return-epilogue pair (LoadConst/ReturnValue
  or LoadSmallInt/ReturnValue) so the epilogue keeps the correct
  line number.
- Rename `preds` to `predecessor_blocks` in the LOAD_FAST_BORROW
  disable pass and add a test-only `debug_late_cfg_trace` helper.
- Regenerate the `nested_double_async_with` snapshot: the tail
  reference to `stop_exc` now emits LOAD_FAST instead of
  LOAD_FAST_BORROW.

* Bytecode parity - iter folding, break/continue line, cold inlining

- Fold a constant list iterable into a constant tuple in for-loop
  iterable position, matching the CPython optimizer, and strip a
  redundant LIST_TO_TUPLE immediately before GET_ITER in the IR
  peephole pass.
- Emit a Nop at the break/continue source range before unwinding
  so line events land on the break/continue statement instead of
  the following instruction.
- Drop `propagate_disable_load_fast_borrow`; the forward propagation
  was over-zealous and the per-block inheritance in `new_block` plus
  the bare-except marker are enough.
- Relax `inline_small_or_no_lineno_blocks` so small exit blocks at
  the tail of a cold block are always inlined, not just return
  epilogues.
- Add codegen tests covering the LIST_TO_TUPLE/GET_ITER peephole and
  the late-CFG trace helper for a for-loop list-literal iterable.
2026-04-18 09:19:11 +09:00
Shahar Naveh
4f1cf6d401 Fix reviewdog permissions (#7619) 2026-04-17 20:07:19 +09:00
Shahar Naveh
3e1aa7cbe6 Update test_itertools.py from 3.14.4 (#7618) 2026-04-17 20:06:48 +09:00
Shahar Naveh
b9f9ba145e Add const methods for oparg enums (#7617)
* Add const methods for oparg enums

* Shorten doc link
2026-04-17 20:06:30 +09:00
Shahar Naveh
3d91197b38 Constify OpArgState methods (#7616) 2026-04-17 20:05:37 +09:00
Shahar Naveh
2827eca293 Simplify Intruction.deopt() (#7615)
* Simplify `Instruction.deopt()`

* Adjust `scripts/generate_opcode_metadata.py`
2026-04-17 20:04:22 +09:00
Joshua Megnauth
aac207003f fix: Python-Rust combining char diff in isalnum (#7612)
* fix: Python-Rust combining char diff in isalnum

Related to: #7518

Rust and Python differ on alphanumeric characters. Rust follows the
Unicode standard closer than Python. This means that is_alphanumeric
(char function in Rust) is different from isalnum (Python). To fix the
discrepancy, RustPython needs to mimic Python by rejecting certain
characters. Some classes of combining characters count as alphanumeric
in Rust but not Python. Combining characters are accent marks
that are combined with other characters to create a single grapheme.

It's possible that this PR is not exhaustive. I fixed the combining
character issue BUT I don't know the full range of discrepancies.

* fix: Ignore combining characters in SRE

Closes: #7518
2026-04-17 18:45:43 +09:00
Joshua Megnauth
f82b8d8eb7 Fix compiling with OpenSSL (#7621) 2026-04-17 18:27:49 +09:00
Noa
8d61a2217b Merge pull request #7620 from ShaharNaveh/ci-fix-clippy
CI: fix clippy lints
2026-04-16 15:15:27 -05:00
ShaharNaveh
640cbd7c4a Clippy lints 2026-04-16 16:54:28 +02:00
Shahar Naveh
aa12accdac Update test_struct.py from 3.14.4 (#7614) 2026-04-16 08:52:37 +09:00
Shahar Naveh
fd2117355e Simplify cache_entries match statement (#7613)
* Simplify cache_entries method

* Use `Opcode`
2026-04-16 08:52:11 +09:00
dependabot[bot]
36025386f3 Bump follow-redirects from 1.15.9 to 1.16.0 in /wasm/demo (#7596)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.9 to 1.16.0.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.9...v1.16.0)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-version: 1.16.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-16 03:18:33 +09:00
Shahar Naveh
330aa08488 Macro for defining opcode & instruction enums (#7573)
* Macro for defining opcode & instruction enums

* Convert `Instruction` as well

* revert `#[repr(...)]` changes
2026-04-16 03:18:14 +09:00
Shahar Naveh
a2c3e65b81 Unify CI caches (reduce total cache size by 75% ±) (#7610)
* Singular job for creating caches

* Use shared cache
2026-04-15 14:19:17 +09:00
Shahar Naveh
8108b6a153 Update test_bisect.py from 3.14.4 (#7609) 2026-04-15 14:17:48 +09:00
Shahar Naveh
63a1c0e95c Update test_bz2.py from 3.14.4 (#7608)
* Update `test_bz2.py` from 3.14.4

* Mark failing test
2026-04-15 14:17:27 +09:00
Shahar Naveh
c98939a7c1 Update seq tests from 3.14.4 (#7606)
* Update seq related tests
2026-04-15 14:17:01 +09:00
Shahar Naveh
9f1429d95f Update test_compile.py from 3.14.4 (#7607) 2026-04-15 14:15:48 +09:00
Shahar Naveh
73218f42d5 Update test_range.py from 3.14.4 (#7605) 2026-04-15 14:15:27 +09:00
dependabot[bot]
f197699e3c Bump reviewdog/action-actionlint from 1.71.0 to 1.72.0
Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.71.0 to 1.72.0.
- [Release notes](https://github.com/reviewdog/action-actionlint/releases)
- [Commits](0d952c597e...6fb7acc99f)

---
updated-dependencies:
- dependency-name: reviewdog/action-actionlint
  dependency-version: 1.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-15 14:14:09 +09:00
ShaharNaveh
898da7f58c Udpate http from 3.14.4 2026-04-14 21:02:25 +09:00
ShaharNaveh
0ee07e3d0a Update test_http_cookies.py from 3.14.3 2026-04-14 21:02:25 +09:00
Jeong, YunWon
49048504f6 Merge pull request #7600 from ShaharNaveh/update-test-urllib 2026-04-14 21:02:07 +09:00
Jeong, YunWon
e3997ad1b8 Merge pull request #7597 from RustPython/dependabot/cargo/rand-0.9.3 2026-04-14 21:01:45 +09:00
ShaharNaveh
da01e617de Update libc to 0.2.185 2026-04-14 21:00:12 +09:00
Jeong, YunWon
891538d924 Merge pull request #7599 from ShaharNaveh/update-test-pyexpat 2026-04-14 20:59:41 +09:00
ShaharNaveh
3096d77ec5 Marking failing test 2026-04-14 13:04:50 +02:00
ShaharNaveh
9e2d03428c Add XMLParserType 2026-04-14 13:03:59 +02:00
ShaharNaveh
d1e9763ff3 Merge remote-tracking branch 'upstream/main' into update-test-pyexpat 2026-04-14 12:15:22 +02:00
dependabot[bot]
2b1b0ba805 Bump the wasmtime group with 2 updates (#7591)
Bumps the wasmtime group with 2 updates: [cranelift-jit](https://github.com/bytecodealliance/wasmtime) and [cranelift-module](https://github.com/bytecodealliance/wasmtime).


Updates `cranelift-jit` from 0.130.0 to 0.130.1
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)

Updates `cranelift-module` from 0.130.0 to 0.130.1
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)

---
updated-dependencies:
- dependency-name: cranelift-jit
  dependency-version: 0.130.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: wasmtime
- dependency-name: cranelift-module
  dependency-version: 0.130.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: wasmtime
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 16:08:35 +09:00
github-actions[bot]
e09b66dd86 Update doc DB for CPython 3.14.4 (#7598)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-14 16:07:22 +09:00
ShaharNaveh
a24ee58961 Remove newlines from test_urllib2_localnet.py 2026-04-14 08:58:51 +02:00
ShaharNaveh
1721f62804 Udpate test_pyexpat.py from 3.14.4 2026-04-14 08:57:35 +02:00
dependabot[bot]
8f71ff4b47 Bump rand from 0.9.2 to 0.9.3
Bumps [rand](https://github.com/rust-random/rand) from 0.9.2 to 0.9.3.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/0.9.3/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/rand_core-0.9.2...0.9.3)

---
updated-dependencies:
- dependency-name: rand
  dependency-version: 0.9.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 01:22:41 +00:00
Joshua Megnauth
4bbabe5810 fix: Handle char expansion in islower, isupper (#7583)
* fix: Handle char expansion in islower, isupper

Closes: #7526

`py_islower` and `py_isupper` need to handle expansions for letter
casing. Comparing chars directly can miss edge cases in certain
languages. Unfortunately, like the last PR, this allocates to handle
potential expansions.

I also had to add `icu_casemap` as a dependency.
RustPython is already using parts of icu4x so this doesn't add many
transitive dependencies.

* Ensure islower/isupper handles strs without chars

This fixes a regression mentioned by CodeRabbit. I also figured out how
to check a string's case without allocation using Unicode properties.
Thus, this commit removes `icu_casemap` again. `icu_casemap` and my old
solution is required for a robust case check, but it seems like the
current code is fine for Python.
2026-04-14 10:19:31 +09:00
Bas Schoenmaeckers
7544628268 Allow creating PyCapsule objects (#7595) 2026-04-14 09:44:02 +09:00
Copilot
7e637e8cbd Add __callback__ property to weakref type (#7590)
* Initial plan

* Add __callback__ property to PyWeak for weakref compatibility

Add get_callback() method to PyWeak in core.rs that safely reads the
callback field under the stripe lock. Expose it as a read-only
__callback__ property on the weakref type, matching CPython behavior:
- Returns the callback function when one was provided and referent is alive
- Returns None when no callback was provided
- Returns None after the referent has been collected
- Raises AttributeError on assignment (read-only)

Remove @unittest.expectedFailure from test_callback_attribute and
test_callback_attribute_after_deletion in test_weakref.py.

Add regression tests to extra_tests/snippets/stdlib_weakref.py.

Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/a8689daa-4476-4645-a935-0e13c7f7bb42

Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>

* Fix ruff formatting in stdlib_weakref.py snippet

Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/4995198f-e083-4dac-823a-166fcf54adc4

Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
2026-04-13 23:58:58 +09:00
Jeong, YunWon
7e5e026941 Add InstructionMetadata::stack_effect_jump for branch stack effects (#7585)
* Add InstructionMetadata::stack_effect_jump for branch stack effects

CPython's compile.c provides stack_effect(opcode, oparg, jump) where the
jump parameter selects between fallthrough and branch effects. The existing
stack_effect() only returns the fallthrough effect.

Add stack_effect_jump() that returns the branch effect. Most instructions
have identical fallthrough/branch effects; ForIter and Send are the
exceptions (ForIter: fallthrough=+1, branch=-1; Send: fallthrough=0,
branch=-1).

* apply review
2026-04-13 17:25:26 +09:00
🍌Shawn
27aed85599 Update LICENSE (#7581) 2026-04-13 12:49:32 +09:00
Jeong, YunWon
d201c48e1c Bytecode parity - for-loop cleanup, return duplication, late jump threading fix (#7580)
- Use POP_TOP instead of POP_ITER for for-loop break/return cleanup
- Expand duplicate_end_returns to clone final return for jump predecessors
- Restrict late jump threading pass to unconditional jumps only
- Skip exception blocks in inline/reorder passes
- Simplify threaded_jump_instr NoInterrupt handling
2026-04-12 22:43:35 +09:00
Shahar Naveh
9cf7bcd64a Fail if prek fails (#7574) 2026-04-10 18:50:03 +09:00
Shahar Naveh
31edcfa97e Remove overly broad permissions from release.yml (#7577) 2026-04-10 18:49:37 +09:00
Jeong, YunWon
eac45727d2 Bytecode parity - direct loop backedges (#7578) 2026-04-10 18:48:57 +09:00
Shahar Naveh
28acbc66f9 Revert "Only run cargo check when rust code is changed (#7572)" (#7579) 2026-04-10 18:07:11 +09:00
Jeong, YunWon
a49ce5bf34 Bytecode parity - exception (#7557)
* Fix exception handling: except* chaining, finally cleanup, RERAISE

- Align except* bytecode chaining
- Fix exception state model and finally handler cleanup
- Fix RERAISE to only pop exception, preserve values below

* Port IR optimization passes from flowgraph.c

- BUILD_TUPLE n + UNPACK_SEQUENCE n elimination
- Dead store elimination within basic blocks
- apply_static_swaps for SWAP reduction

* Add bytecode comparison and disassembly dump scripts

- compare_bytecode.py: compare CPython vs RustPython bytecode output
- dis_dump.py: extract disassembly in normalized JSON format
2026-04-10 12:51:48 +09:00
Shahar Naveh
7b5ac61026 Update some libs and tests from 3.14.3 to 3.14.4 (#7576)
* Update some libs and tests to 3.14.4

* Update `pydoc_data`
2026-04-10 09:16:10 +09:00
Shahar Naveh
ad66d9acd0 Only run cargo check when rust code is changed (#7572) 2026-04-10 02:40:15 +09:00
Shahar Naveh
00dd9a5ed1 Unify python version used by CI. Update to 3.14.4 (#7571)
* Add `.python-version` file

* Modify CI to use `.python-version` file

* Use correct path for `.python-version` file
2026-04-10 02:38:14 +09:00
dependabot[bot]
d5a90e5c1f Bump cargo-bins/cargo-binstall from 1.17.8 to 1.17.9 (#7566)
Bumps [cargo-bins/cargo-binstall](https://github.com/cargo-bins/cargo-binstall) from 1.17.8 to 1.17.9.
- [Release notes](https://github.com/cargo-bins/cargo-binstall/releases)
- [Changelog](https://github.com/cargo-bins/cargo-binstall/blob/main/release-plz.toml)
- [Commits](113a77a4ce...0b24824336)

---
updated-dependencies:
- dependency-name: cargo-bins/cargo-binstall
  dependency-version: 1.17.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-06 23:30:13 +09:00
dependabot[bot]
72f397c6df Bump pyo3 from 0.28.2 to 0.28.3 in the pyo3 group (#7563)
Bumps the pyo3 group with 1 update: [pyo3](https://github.com/pyo3/pyo3).


Updates `pyo3` from 0.28.2 to 0.28.3
- [Release notes](https://github.com/pyo3/pyo3/releases)
- [Changelog](https://github.com/PyO3/pyo3/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pyo3/pyo3/compare/v0.28.2...v0.28.3)

---
updated-dependencies:
- dependency-name: pyo3
  dependency-version: 0.28.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: pyo3
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-06 23:29:58 +09:00
Joshua Megnauth
e009cc0c3b fix: Swapcase must handle multibyte expansions (#7559)
`swapcase` used `to_ascii_lowercase` and uppercase to swap cases. This
is fine for ASCII, but code points may expand into multiple bytes which
leads to incorrect case swaps for some languages. The fix is to use
`to_lowercase` and `to_uppercase` instead.

Unfortunately, this leads to a realloc in `swapcase` when bytes are
expanded.

Part of #7526.
2026-04-06 23:29:30 +09:00
Copilot
eed618d858 Fix str.zfill() width calculation for non-ASCII strings (#7534) 2026-04-04 14:45:30 +09:00
Bo Maryniuk
87fc4540c4 Fix VM's infinite recursion crash with musl libc (#7558)
* Fix VM's infinite recursion crash with musl libc

* Lintfix/cleanup warnings
2026-04-04 09:46:46 +09:00
dependabot[bot]
a09afab912 Bump lodash from 4.17.23 to 4.18.1 in /wasm/demo (#7556)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-03 01:44:50 +09:00
Shahar Naveh
3d9688402a Replace unmaintained unic crates (#7555) 2026-04-03 01:43:11 +09:00
Shahar Naveh
b61dfdc534 Updtae test_optimizer.py from 3.14.3 (#7554) 2026-04-03 01:33:50 +09:00
Shahar Naveh
6d7d74cc0b Update test_opcache.py from 3.14.3 (#7553) 2026-04-03 01:33:34 +09:00
Shahar Naveh
3f49f42702 Update test_module from 3.14.3 (#7552)
* Update `test_module` from 3.14.3

* Unmark passing test
2026-04-03 01:33:19 +09:00
Shahar Naveh
5afa3493a1 Add test_perfmaps.py from 3.14.3 (#7551) 2026-04-03 00:50:25 +09:00
Shahar Naveh
1adda8a73d Update test_file & test_largefile from 3.14.3 (#7550) 2026-04-03 00:49:55 +09:00
Shahar Naveh
344b7a5abd [zizmor] ignore superfluous-actions (#7548) 2026-04-03 00:44:58 +09:00
Jeong, YunWon
d9c4c95369 fix crates (#7549) 2026-03-31 21:05:08 +09:00
Jeong, YunWon
403c2be01d Improve codegen bytecode parity (#7541)
- Add CFG block splitting, jump threading, backward jump normalization
- Add genexpr StopIteration wrapper
- Add ConstantData::Slice and constant slice folding
- Add duplicate_exits_without_lineno and Block: Clone
- Add builtin(genexpr) optimization for tuple/list/set/all/any
- Add compile_try_except_no_finally for try-except without finally
- Add module_name_declared_global_in_nested_scope
- Add constant tuple folding in try_fold_constant_expr
- Add fstring literal-only optimization and empty literal elision
- Fix duplicate_exits_without_lineno: splice new blocks into linked list
2026-03-31 15:45:18 +09:00
Shahar Naveh
5cc9eab2dd Resolve excessive-permissions warning in ci.yaml (#7547)
* Resolve `excessive-permissions` warning in `ci.yaml`

* Update .github/workflows/ci.yaml

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-03-31 14:53:08 +09:00
dependabot[bot]
b275a90cf9 Bump j178/prek-action from 2.0.0 to 2.0.1 (#7544)
Bumps [j178/prek-action](https://github.com/j178/prek-action) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/j178/prek-action/releases)
- [Commits](79f765515b...53276d8b0d)

---
updated-dependencies:
- dependency-name: j178/prek-action
  dependency-version: 2.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 18:38:01 +02:00
dependabot[bot]
43851c21b9 Bump minimatch and serve in /wasm/demo (#7539)
Bumps [minimatch](https://github.com/isaacs/minimatch) to 3.1.5 and updates ancestor dependency [serve](https://github.com/vercel/serve). These dependencies need to be updated together.


Updates `minimatch` from 3.1.2 to 3.1.5
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5)

Updates `serve` from 14.2.5 to 14.2.6
- [Release notes](https://github.com/vercel/serve/releases)
- [Changelog](https://github.com/vercel/serve/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vercel/serve/compare/v14.2.5...v14.2.6)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 3.1.5
  dependency-type: indirect
- dependency-name: serve
  dependency-version: 14.2.6
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-31 01:31:08 +09:00
Shahar Naveh
611b122ed7 Resolve template injection (#7546) 2026-03-31 01:30:41 +09:00
dependabot[bot]
1a4964b741 Bump dtolnay/rust-toolchain (#7545)
Bumps [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain) from efa25f7f19611383d5b0ccf2d1c8914531636bf9 to 3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9.
- [Release notes](https://github.com/dtolnay/rust-toolchain/releases)
- [Commits](efa25f7f19...3c5f7ea28c)

---
updated-dependencies:
- dependency-name: dtolnay/rust-toolchain
  dependency-version: 3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-31 01:30:08 +09:00
dependabot[bot]
106f1c9f37 Bump env_logger from 0.11.9 to 0.11.10 (#7543)
Bumps [env_logger](https://github.com/rust-cli/env_logger) from 0.11.9 to 0.11.10.
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.9...v0.11.10)

---
updated-dependencies:
- dependency-name: env_logger
  dependency-version: 0.11.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-31 01:29:47 +09:00
dependabot[bot]
c45f69977b Bump schannel from 0.1.28 to 0.1.29 (#7542)
Bumps [schannel](https://github.com/steffengy/schannel-rs) from 0.1.28 to 0.1.29.
- [Release notes](https://github.com/steffengy/schannel-rs/releases)
- [Commits](https://github.com/steffengy/schannel-rs/compare/v0.1.28...v0.1.29)

---
updated-dependencies:
- dependency-name: schannel
  dependency-version: 0.1.29
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-31 01:29:35 +09:00
Shahar Naveh
2703f94c3e Split cargo check matrix to individual targets. Avoid cache poisoning (#7540)
* Split check matrix. Prevent cache poisoning

* Use `rustup`

* Change name

* Align cargo args
2026-03-30 23:59:48 +09:00
Shahar Naveh
9900c761ca Fix lint warnings in release.yml (#7538) 2026-03-30 18:53:38 +09:00
Shahar Naveh
959b088d25 Remove oparg builders (#7537) 2026-03-30 18:53:13 +09:00
Jeong, YunWon
1c39fdb7f9 Bytecode parity (#7536)
* Add CFG block splitting, jump threading, backward jump normalization, genexpr StopIteration wrapper

- split_blocks_at_jumps: split blocks at branch points so each has one exit
- jump_threading: thread jumps through single-jump blocks (flowgraph.c jump_thread)
- Backward conditional jump normalization: invert and create NOT_TAKEN+JUMP block
- Follow empty blocks in jump-to-return optimization (next_nonempty_block)
- Add PEP 479 StopIteration handler to compile_comprehension for generators

* Add ConstantData::Slice and constant slice folding

- Add Slice variant to ConstantData and BorrowedConstant
- Fold constant slices (x[:3], x[1:4]) into LOAD_CONST(slice(...))
- Marshal serialization/deserialization for Slice type
- Box::leak in borrow_obj_constant for PySlice roundtrip

* Add duplicate_exits_without_lineno (disabled) and Block: Clone

Prepare infrastructure for exit block duplication optimization.
Currently disabled pending stackdepth integration.

* Improve codegen bytecode parity
2026-03-30 18:50:58 +09:00
Jeong, YunWon
3706c5376e Bytecode parity (#7535)
* Add CFG block splitting, jump threading, backward jump normalization, genexpr StopIteration wrapper

- split_blocks_at_jumps: split blocks at branch points so each has one exit
- jump_threading: thread jumps through single-jump blocks (flowgraph.c jump_thread)
- Backward conditional jump normalization: invert and create NOT_TAKEN+JUMP block
- Follow empty blocks in jump-to-return optimization (next_nonempty_block)
- Add PEP 479 StopIteration handler to compile_comprehension for generators

* Add ConstantData::Slice and constant slice folding

- Add Slice variant to ConstantData and BorrowedConstant
- Fold constant slices (x[:3], x[1:4]) into LOAD_CONST(slice(...))
- Marshal serialization/deserialization for Slice type
- Box::leak in borrow_obj_constant for PySlice roundtrip

* Add ConstantData::Frozenset variant (type only, folding deferred)

Add Frozenset to ConstantData, BorrowedConstant, and marshal support.
Actual frozenset folding (BUILD_SET + CONTAINS_OP → LOAD_CONST frozenset)
requires VirtualMachine for element hashing and is deferred.

* Add duplicate_exits_without_lineno (disabled) and Block: Clone

Prepare infrastructure for exit block duplication optimization.
Currently disabled pending stackdepth integration.
2026-03-30 12:52:04 +09:00
Huy Vũ (Josh)
e6bcd64066 Validate SyntaxError details tuple shape (#7533) 2026-03-29 22:37:08 +09:00
Jeong, YunWon
2ebd7026e4 Compiler parity: docstring dedent, StopIteration wrapper, constant folding (#7530) 2026-03-29 22:36:20 +09:00
Jeong, YunWon
6826557884 ruff from fork (#7532) 2026-03-29 19:26:24 +09:00
Jeong, YunWon
1f6b4c6bf1 ruff 0.15.8 (#7531) 2026-03-29 15:21:09 +09:00
802 changed files with 125410 additions and 34009 deletions

View File

@@ -4,6 +4,8 @@ argdefs
argtypes
asdl
asname
atopen
atext
attro
augassign
badcert
@@ -30,9 +32,12 @@ cellvar
cellvars
ceval
cfield
cfws
CFWS
CLASSDEREF
classdict
cmpop
CNOTAB
codedepth
CODEUNIT
CONIN
@@ -47,13 +52,16 @@ datastack
defaultdict
denom
deopt
deopts
dictbytype
DICTFLAG
dictoffset
distpoint
dynload
elts
eooh
eofs
EOOH
evalloop
excepthandler
exceptiontable
@@ -62,6 +70,7 @@ fastlocals
fblock
fblocks
fdescr
fdst
ffi_argtypes
fielddesc
fieldlist
@@ -75,6 +84,7 @@ freelist
freevar
freevars
fromlist
fsrc
getdict
getfunc
getiter
@@ -89,28 +99,39 @@ HASUNION
heaptype
hexdigit
HIGHRES
ialloc
IFUNC
IMMUTABLETYPE
INCREF
inlinedepth
inplace
inpos
ioffset
isbytecode
ishidden
ismine
ISPOINTER
isoctal
iteminfo
Itertool
iused
keeped
kwnames
kwonlyarg
kwonlyargs
kwonlydefaults
lasti
libffi
linearise
lineful
lineiterator
linetable
LNOTAB
loadfast
localsplus
localspluskinds
Lshift
lslpp
lsprof
MAXBLOCKS
maxdepth
@@ -120,16 +141,23 @@ mult
multibytecodec
nameobj
nameop
nargsf
nblocks
ncells
ncellsused
ncellvars
nconsts
newargs
newfree
NEWLOCALS
newsemlockobject
nextop
nfrees
nkwargs
nkwelts
nlocalsplus
nointerrupt
noffsets
Nondescriptor
noninteger
nops
@@ -137,6 +165,7 @@ noraise
nseen
NSIGNALS
numer
nvars
opname
opnames
orelse
@@ -149,18 +178,22 @@ patma
peepholer
phcount
platstdlib
ploc
posonlyarg
posonlyargs
prec
preds
preinitialized
pybuilddir
pycore
pyinner
pydecimal
pyerrors
Pyfunc
pylifecycle
pymain
pyrepl
pystate
PYTHONTRACEMALLOC
PYTHONUTF8
pythonw
@@ -168,6 +201,7 @@ PYTHREAD_NAME
releasebuffer
repr
resinfo
retarget
Rshift
SA_ONSTACK
saveall
@@ -179,6 +213,7 @@ SETREF
setresult
setslice
settraceallthreads
sget
SLOTDEFINED
SMALLBUF
SOABI
@@ -189,13 +224,16 @@ staticbase
stginfo
storefast
stringlib
stringized
structseq
subkwargs
subparams
subscr
sval
swappedbytes
swaptimize
sysdict
tbstderr
templatelib
testconsole
threadstate
@@ -214,6 +252,7 @@ uncollectable
Unhandle
unparse
unparser
untargeted
untracking
VARKEYWORDS
varkwarg

View File

@@ -67,6 +67,7 @@ fillchar
fillvalue
finallyhandler
firstiter
fobj
firstlineno
fnctl
frombytes
@@ -111,12 +112,14 @@ idfunc
idiv
idxs
impls
infd
indexgroup
infj
inittab
Inittab
instancecheck
instanceof
instrs
interpchannels
interpqueues
irepeat
@@ -175,6 +178,7 @@ Nonprintable
onceregistry
origname
ospath
outfd
pendingcr
phello
platlibdir
@@ -185,10 +189,12 @@ posonlyargcount
prepending
profilefunc
pycache
pycapsule
pycodecs
pycs
pydatetime
pyexpat
PYGILSTATE
pyio
pymain
PYTHONAPI

View File

@@ -49,20 +49,25 @@
"ignorePaths": [
"**/__pycache__/**",
"target/**",
"Lib/**"
"Lib/**",
"crates/host_env/**"
],
// words - list of words to be always considered correct
// (compound words like pyarg, baseclass, microbenchmark are handled by allowCompoundWords)
"words": [
"aiterable",
"alnum",
"csock",
"coro",
"dedentations",
"dedents",
"deduped",
"deoptimized",
"deoptimize",
"emscripten",
"excs",
"fnfe",
"ifexp",
"interps",
"jitted",
"jitting",
@@ -72,8 +77,13 @@
"oparg",
"opargs",
"pyc",
"reborrow",
"reraises",
"reraising",
"significand",
"summands",
"TESTFN",
"TZPATH",
"unraisable",
"wasi",
"weaked",

19
.gitattributes vendored
View File

@@ -58,13 +58,14 @@ Lib/venv/scripts/posix/* text eol=lf
#
[attr]generated linguist-generated=true diff=generated
Lib/_opcode_metadata.py generated
Lib/keyword.py generated
Lib/idlelib/help.html generated
Lib/test/certdata/*.pem generated
Lib/test/certdata/*.0 generated
Lib/test/levenshtein_examples.json generated
Lib/test/test_stable_abi_ctypes.py generated
Lib/token.py generated
Lib/_opcode_metadata.py generated
Lib/keyword.py generated
Lib/idlelib/help.html generated
Lib/test/certdata/*.pem generated
Lib/test/certdata/*.0 generated
Lib/test/levenshtein_examples.json generated
Lib/test/test_stable_abi_ctypes.py generated
Lib/token.py generated
crates/compiler-core/src/bytecode/opcode_metadata.rs generated
.github/workflows/*.lock.yml linguist-generated=true merge=ours
.github/workflows/*.lock.yml linguist-generated=true merge=ours

View File

@@ -2,7 +2,9 @@
version: 2
updates:
- package-ecosystem: cargo
directory: /
directories:
- "/"
- "crates/*"
schedule:
interval: weekly
cooldown:
@@ -19,6 +21,7 @@ updates:
- "digest"
- "md-5"
- "sha-1"
- "sha1"
- "sha2"
- "sha3"
- "blake2"
@@ -120,6 +123,11 @@ updates:
toml:
patterns:
- "toml*"
unix:
patterns:
- "mac_address"
- "nix"
- "rustyline"
wasm-bindgen:
patterns:
- "wasm-bindgen*"

View File

@@ -8,6 +8,9 @@ on:
name: CI
permissions:
contents: read
# Cancel previous workflows if they are the same workflow on same ref (branch/tags)
# with the same event (push/pull_request) even they are in progress.
# This setting will help reduce the number of duplicated workflows.
@@ -16,19 +19,63 @@ concurrency:
cancel-in-progress: true
env:
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls,host_env
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls-aws-lc,host_env
CARGO_ARGS_NO_SSL: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,host_env
# Crates excluded from workspace builds:
# - rustpython_wasm: requires wasm target
# - rustpython-compiler-source: deprecated
# - rustpython-venvlauncher: Windows-only
WORKSPACE_EXCLUDES: --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher
# Python version targeted by the CI.
PYTHON_VERSION: "3.14.3"
X86_64_PC_WINDOWS_MSVC_OPENSSL_LIB_DIR: C:\Program Files\OpenSSL\lib\VC\x64\MD
X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR: C:\Program Files\OpenSSL\include
CARGO_INCREMENTAL: 0
CARGO_PROFILE_TEST_DEBUG: 0
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_RELEASE_DEBUG: 0
CARGO_TERM_COLOR: always
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true # TODO: Remove on 2026/06/02
CI: true
jobs:
determine_changes:
name: Determine changes
runs-on: ubuntu-slim
outputs:
# Flag that is raised when any rust code is changed.
rust_code: ${{ steps.check_rust_code.outputs.changed }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Determine merge base
id: merge_base
run: |
sha=$(git merge-base HEAD "origin/${BASE_REF}")
echo "sha=${sha}" >> "$GITHUB_OUTPUT"
env:
BASE_REF: ${{ github.event.pull_request.base.ref || 'main' }}
- name: Check if there was any code related change
id: check_rust_code
run: |
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
':Cargo.toml' \
':Cargo.lock' \
':rust-toolchain.toml' \
':.cargo/config.toml' \
':crates/**' \
':src/**' \
':.github/workflows/ci.yaml' \
; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
env:
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
rust_tests:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
env:
@@ -45,29 +92,36 @@ jobs:
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
components: clippy
toolchain: stable
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Restore cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}-
restore-keys: |
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
${{ runner.os }}-stable--
# Windows runners randomly crashes, https://github.com/actions/cache/issues/1754
continue-on-error: true
- name: Install macOS dependencies
uses: ./.github/actions/install-macos-deps
- name: run clippy
run: cargo clippy ${{ env.CARGO_ARGS }} --workspace --all-targets ${{ env.WORKSPACE_EXCLUDES }} -- -Dwarnings
- name: run rust tests
run: cargo test --workspace ${{ env.WORKSPACE_EXCLUDES }} --verbose --features threading ${{ env.CARGO_ARGS }}
run: cargo test --workspace --exclude rustpython-capi ${{ env.WORKSPACE_EXCLUDES }} --features threading ${{ env.CARGO_ARGS }}
env:
INSTA_WORKSPACE_ROOT: ${{ github.workspace }}
- name: check compilation without threading
run: cargo check ${{ env.CARGO_ARGS }}
- run: cargo doc --locked
if: runner.os == 'Linux'
- name: run c-api tests
working-directory: crates/capi
run: cargo test
if: runner.os != 'Windows' # Requires pyo3 0.29+ on Windows
- name: check compilation without host_env (sandbox mode)
run: |
@@ -107,42 +161,49 @@ jobs:
if: runner.os == 'Linux'
cargo_check:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
name: Ensure compilation on various targets
name: cargo check
runs-on: ${{ matrix.os }}
needs:
- determine_changes
if: |
(
!contains(github.event.pull_request.labels.*.name, 'skip:ci') &&
needs.determine_changes.outputs.rust_code == 'true'
) || github.ref == 'refs/heads/main'
strategy:
matrix:
include:
include:
- os: ubuntu-latest
targets:
- aarch64-linux-android
- i686-unknown-linux-gnu
- i686-unknown-linux-musl
- wasm32-wasip2
- x86_64-unknown-freebsd
target: aarch64-linux-android
- os: ubuntu-latest
target: i686-unknown-linux-gnu
dependencies:
gcc-multilib: true
musl-tools: true
- os: ubuntu-latest
targets:
- aarch64-unknown-linux-gnu
target: i686-unknown-linux-musl
dependencies:
gcc-aarch64-linux-gnu: true # conflict with `gcc-multilib`
musl-tools: true
skip_ssl: true
- os: ubuntu-latest
target: wasm32-wasip2
skip_ssl: true
- os: ubuntu-latest
target: x86_64-unknown-freebsd
skip_ssl: true
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
dependencies:
gcc-aarch64-linux-gnu: true
- os: macos-latest
targets:
- aarch64-apple-ios
- x86_64-apple-darwin
target: aarch64-apple-ios
- os: macos-latest
target: x86_64-apple-darwin
fail-fast: false
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
prefix-key: v0-rust-${{ join(matrix.targets, '-') }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install dependencies
uses: ./.github/actions/install-linux-deps
# zizmor has an issue with dynamic `with`
@@ -152,19 +213,49 @@ jobs:
musl-tools: ${{ matrix.dependencies.musl-tools || false }}
gcc-aarch64-linux-gnu: ${{ matrix.dependencies.gcc-aarch64-linux-gnu || false }}
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
- name: Restore cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
targets: ${{ join(matrix.targets, ',') }}
toolchain: stable
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
# key won't match, will rely on restore-keys
key: ${{ runner.os }}-${{ matrix.target }}
restore-keys: |
${{ runner.os }}-stable-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml') }}-
${{ runner.os }}-stable-${{ matrix.target }}-
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
${{ runner.os }}-stable--
- uses: dtolnay/rust-toolchain@stable
with:
target: ${{ matrix.target }}
- name: Setup Android NDK
if: ${{ contains(matrix.targets, 'aarch64-linux-android') }}
if: ${{ matrix.target == 'aarch64-linux-android' }}
id: setup-ndk
uses: nttld/setup-ndk@v1
uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
with:
ndk-version: r27
add-to-path: true
- name: Append env conf to cargo
if: ${{ matrix.target == 'aarch64-linux-android' }}
env:
NDK_PATH: ${{ steps.setup-ndk.outputs.ndk-path }}
run: |
{
echo "[env]"
echo "CC_aarch64_linux_android = \"${NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang\""
echo "AR_aarch64_linux_android = \"${NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\""
echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER = \"${NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang\""
} >> .cargo/config.toml
# - name: Prepare repository for redox compilation
# run: bash scripts/redox/uncomment-cargo.sh
# - name: Check compilation for Redox
@@ -173,18 +264,12 @@ jobs:
# command: check
# args: --ignore-rust-version
- name: Check compilation
run: |
for target in ${{ join(matrix.targets, ' ') }}
do
echo "::group::${target}"
cargo check --target $target ${{ env.CARGO_ARGS_NO_SSL }}
echo "::endgroup::"
done
env:
CC_aarch64_linux_android: ${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang
AR_aarch64_linux_android: ${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar
CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: ${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang
- name: Check compilation with threading
run: cargo check --target "${{ matrix.target }}" ${{ env.CARGO_ARGS_NO_SSL }} --features threading
- name: Check compilation with ssl
if: ${{ !matrix.skip_ssl }}
run: cargo check --target "${{ matrix.target }}" ${{ env.CARGO_ARGS }}
snippets_cpython:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
@@ -206,18 +291,21 @@ jobs:
- os: macos-latest
extra_test_args:
- '-u all'
env_polluting_tests: []
env_polluting_tests:
- test_set
skips: []
timeout: 50
- os: ubuntu-latest
extra_test_args:
- '-u all'
env_polluting_tests: []
env_polluting_tests:
- test_set
skips: []
timeout: 60
- os: windows-2025
extra_test_args: [] # TODO: Enable '-u all'
env_polluting_tests: []
env_polluting_tests:
- test_set
skips:
- test_rlcompleter
- test_pathlib # panic by surrogate chars
@@ -230,17 +318,25 @@ jobs:
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: stable
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Restore cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}-
restore-keys: |
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
${{ runner.os }}-stable--
# Windows runners randomly crashes, https://github.com/actions/cache/issues/1754
continue-on-error: true
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install macOS dependencies
uses: ./.github/actions/install-macos-deps
@@ -270,8 +366,24 @@ jobs:
- name: Run flaky MP CPython tests
run: |
target/release/rustpython -m test -j 1 ${{ join(matrix.extra_test_args, ' ') }} --slowest --fail-env-changed --timeout 600 -v ${{ env.FLAKY_MP_TESTS }}
for attempt in $(seq 1 5); do
echo "::group::Attempt ${attempt}"
set +e
target/release/rustpython -m test -j 1 ${{ join(matrix.extra_test_args, ' ') }} --slowest --fail-env-changed --timeout 600 -v ${{ env.FLAKY_MP_TESTS }}
status=$?
set -e
echo "::endgroup::"
if [ $status -eq 0 ]; then
exit 0
fi
done
exit 1
timeout-minutes: ${{ matrix.timeout }}
shell: bash
env:
RUSTPYTHON_SKIP_ENV_POLLUTERS: true
@@ -328,12 +440,82 @@ jobs:
shell: bash
run: python -I scripts/whats_left.py ${{ env.CARGO_ARGS }} --features jit
clippy:
name: clippy
runs-on: ${{ matrix.os }}
needs:
- determine_changes
permissions:
contents: read
if: |
needs.determine_changes.outputs.rust_code == 'true' ||
github.ref == 'refs/heads/main'
strategy:
fail-fast: false
matrix:
os:
- macos-latest
- ubuntu-latest
- windows-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Restore cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}-
restore-keys: |
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
${{ runner.os }}-stable--
# Windows runners randomly crashes, https://github.com/actions/cache/issues/1754
continue-on-error: true
- name: Clippy
run: cargo clippy --keep-going ${{ env.CARGO_ARGS }} --workspace --all-targets ${{ env.WORKSPACE_EXCLUDES }} -- -Dwarnings
cargo_shear:
name: cargo shear
runs-on: ubuntu-latest
needs:
- determine_changes
permissions:
contents: read
if: |
needs.determine_changes.outputs.rust_code == 'true' ||
github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- uses: cargo-bins/cargo-binstall@aaa84a43aec4955a42c5ffc65d258961e39f276e # v1.19.1
- name: cargo shear
run: |
cargo binstall --no-confirm cargo-shear
cargo shear
lint:
name: Lint
runs-on: ubuntu-latest
permissions:
contents: read
checks: write
issues: write
pull-requests: write
security-events: write # for zizmor
steps:
@@ -342,55 +524,76 @@ jobs:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: rustfmt
- uses: cargo-bins/cargo-binstall@113a77a4ce971c41332f2129c3d995df993cf746 # v1.17.8
- name: cargo shear
run: |
cargo binstall --no-confirm cargo-shear
cargo shear
- name: actionlint
uses: reviewdog/action-actionlint@0d952c597ef8459f634d7145b0b044a9699e5e43 # v1.71.0
uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1.72.0
- name: zizmor
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
- name: restore prek cache
if: ${{ github.ref != 'refs/heads/main' }} # never restore on main
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
path: ~/.cache/prek
- name: prek
# TODO: Remove on 2026/06/02 when node24 is the default
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
package-manager-cache: false
node-version: "24"
- name: install prek
id: prek
uses: j178/prek-action@79f765515bd648eb4d6bb1b17277b7cb22cb6468 # v2.0.0
uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
with:
cache: false
show-verbose-logs: false
continue-on-error: true
install-only: true
- name: prek run
run: prek run --show-diff-on-failure --color=always --all-files
- name: Get target CPython version
id: cpython-version
run: |
version=$(cat .python-version)
echo "version=${version}" >> "$GITHUB_OUTPUT"
- name: Clone CPython
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: python/cpython
path: cpython
ref: "v${{ steps.cpython-version.outputs.version }}"
persist-credentials: false
- name: prek run (manual stage)
run: prek run --show-diff-on-failure --color=always --all-files --hook-stage manual
env:
CPYTHON_ROOT: ${{ github.workspace }}/cpython
- name: save prek cache
if: ${{ github.ref == 'refs/heads/main' }} # only save on main
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
path: ~/.cache/prek
- name: restore git permissions
if: ${{ !cancelled() }}
run: sudo chown -R "$(id -u):$(id -g)" .git
- name: reviewdog
uses: reviewdog/action-suggester@aa38384ceb608d00f84b4690cacc83a5aba307ff # 1.24.0
if: ${{ !cancelled() }}
uses: reviewdog/action-suggester@aa38384ceb608d00f84b4690cacc83a5aba307ff # v1.24.0
with:
level: warning
fail_level: error
cleanup: false
miri:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
@@ -404,14 +607,23 @@ jobs:
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.NIGHTLY_CHANNEL }}
components: miri
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Restore cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}
restore-keys: |
${{ runner.os }}-
- name: Run tests under miri
run: cargo +${{ env.NIGHTLY_CHANNEL }} miri test -p rustpython-vm -- miri_test
@@ -430,17 +642,27 @@ jobs:
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
toolchain: stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Restore cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}-
restore-keys: |
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
${{ runner.os }}-stable--
${{ runner.os }}-
- name: cargo clippy
run: cargo clippy --manifest-path=crates/wasm/Cargo.toml -- -Dwarnings
run: cargo clippy --keep-going --manifest-path=crates/wasm/Cargo.toml -- -Dwarnings
- name: install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
@@ -451,15 +673,29 @@ jobs:
tar -xzf geckodriver-v0.36.0-linux64.tar.gz -C geckodriver
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- run: python -m pip install -r requirements.txt
working-directory: ./wasm/tests
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
cache: "npm"
cache-dependency-path: "wasm/demo/package-lock.json"
package-manager-cache: false
- name: Get npm cache directory
id: npm-cache-dir
shell: bash
run: echo "dir=$(npm config get cache)" >> "$GITHUB_OUTPUT"
- name: Restore npm cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
# don't restore on main or release
if: github.ref != 'refs/heads/main' && github.ref != 'refs/heads/release'
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: node-${{ runner.os }}-wasm-demo-
restore-keys: |
node-${{ runner.os }}-wasm-demo-
- name: run test
run: |
driver_path="$(pwd)/../../geckodriver"
@@ -469,8 +705,11 @@ jobs:
env:
NODE_OPTIONS: "--openssl-legacy-provider"
working-directory: ./wasm/demo
- uses: mwilliamson/setup-wabt-action@v3
with: { wabt-version: "1.0.36" }
- uses: mwilliamson/setup-wabt-action@427f2fdd70bc4dbc2e53c2eb4f19f66162d71bd2 # v4.0.0
with:
wabt-version: "1.0.36"
- name: check wasm32-unknown without js
run: |
cd example_projects/wasm32_without_js/rustpython-without-js
@@ -480,6 +719,7 @@ jobs:
echo "ERROR: wasm32-unknown module expects imports from the host environment" >&2
fi
cargo run --release --manifest-path wasm-runtime/Cargo.toml rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm
- name: build notebook demo
if: github.ref == 'refs/heads/release'
run: |
@@ -489,15 +729,24 @@ jobs:
env:
NODE_OPTIONS: "--openssl-legacy-provider"
working-directory: ./wasm/notebook
- name: Deploy demo to Github Pages
if: success() && github.ref == 'refs/heads/release'
uses: peaceiris/actions-gh-pages@v4
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
env:
ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
PUBLISH_DIR: ./wasm/demo/dist
EXTERNAL_REPOSITORY: RustPython/demo
PUBLISH_BRANCH: master
- name: Save npm cache
# Save only on main or release
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release'
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: node-${{ runner.os }}-wasm-demo-${{ hashFiles('wasm/demo/package-lock.json') }}
wasm-wasi:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
name: Run snippets and cpython tests on wasm-wasi
@@ -508,17 +757,28 @@ jobs:
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
- uses: dtolnay/rust-toolchain@stable
with:
target: wasm32-wasip1
toolchain: stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Restore cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}-
restore-keys: |
${{ runner.os }}-stable-wasm32-wasip1-${{ hashFiles('**/Cargo.toml') }}-
${{ runner.os }}-stable-wasm32-wasip1-
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
${{ runner.os }}-stable--
- name: Setup Wasmer
uses: wasmerio/setup-wasmer@v3
uses: wasmerio/setup-wasmer@24b15c95293d23f89c68bd40dac76338f773e924 # v3.1
- name: Install clang
uses: ./.github/actions/install-linux-deps
@@ -526,8 +786,44 @@ jobs:
clang: true
- name: build rustpython
run: cargo build --release --target wasm32-wasip1 --features freeze-stdlib,stdlib --verbose
run: cargo build --release --target wasm32-wasip1 --no-default-features --features freeze-stdlib,stdlib,stdio,importlib,host_env --verbose
- name: run snippets
run: wasmer run --dir "$(pwd)" target/wasm32-wasip1/release/rustpython.wasm -- "$(pwd)/extra_tests/snippets/stdlib_random.py"
- name: run cpython unittest
run: wasmer run --dir "$(pwd)" target/wasm32-wasip1/release/rustpython.wasm -- "$(pwd)/Lib/test/test_int.py"
cargo_doc:
needs:
- determine_changes
if: |
(
!contains(github.event.pull_request.labels.*.name, 'skip:ci') &&
needs.determine_changes.outputs.rust_code == 'true'
) || github.ref == 'refs/heads/main'
env:
RUST_BACKTRACE: full
name: cargo doc
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- name: Restore cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}-
restore-keys: |
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
${{ runner.os }}-stable--
- name: cargo doc
run: cargo doc --locked

View File

@@ -18,4 +18,6 @@ jobs:
steps:
# Using REST API and not `gh issue edit`. https://github.com/cli/cli/issues/6235#issuecomment-1243487651
- run: |
curl -H "Authorization: token ${{ github.token }}" -d '{"assignees": ["${{ github.event.comment.user.login }}"]}' https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees
curl -H "Authorization: token ${{ github.token }}" -d '{"assignees": ["${{ env.USER }}"]}' https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees
env:
USER: ${{ github.event.comment.user.login }}

View File

@@ -1,3 +1,5 @@
name: Periodic checks/tasks
on:
schedule:
- cron: "0 0 * * 6"
@@ -5,15 +7,15 @@ on:
push:
paths:
- .github/workflows/cron-ci.yaml
branches:
- main
pull_request:
paths:
- .github/workflows/cron-ci.yaml
name: Periodic checks/tasks
env:
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls,jit,host_env
PYTHON_VERSION: "3.14.3"
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls-aws-lc,jit,host_env
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' # TODO: Remove on 2026/06/02
jobs:
# codecov collects code coverage data from the rust tests, python snippets and python test suite.
@@ -29,24 +31,32 @@ jobs:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@cargo-llvm-cov
- uses: actions/setup-python@v6.2.0
- uses: taiki-e/install-action@b550161ef8a7bc4f2a671c0b03a18ac9ccedea1e # v2.79.1
with:
python-version: ${{ env.PYTHON_VERSION }}
tool: cargo-llvm-cov
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- run: sudo apt-get update && sudo apt-get -y install lcov
- name: Run cargo-llvm-cov with Rust tests.
run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher --verbose --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls,jit,host_env
run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher --verbose --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls-aws-lc,jit,host_env
- name: Run cargo-llvm-cov with Python snippets.
run: python scripts/cargo-llvm-cov.py
continue-on-error: true
- name: Run cargo-llvm-cov with Python test suite.
run: cargo llvm-cov --no-report run -- -m test -u all --slowest --fail-env-changed
continue-on-error: true
- name: Prepare code coverage data
run: cargo llvm-cov report --lcov --output-path='codecov.lcov'
- name: Upload to Codecov
if: ${{ github.event_name != 'pull_request' }}
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
files: ./codecov.lcov
@@ -61,12 +71,15 @@ jobs:
persist-credentials: true
- uses: dtolnay/rust-toolchain@stable
- name: build rustpython
run: cargo build --release --verbose
- name: collect tests data
run: cargo run --release extra_tests/jsontests.py
env:
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
- name: upload tests data to the website
if: ${{ github.event_name != 'pull_request' }}
env:
@@ -96,17 +109,19 @@ jobs:
persist-credentials: true
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-python@v6.2.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- name: build rustpython
run: cargo build --release --verbose
- name: Collect what is left data
run: |
chmod +x ./scripts/whats_left.py
./scripts/whats_left.py --features "ssl,sqlite" > whats_left.temp
env:
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
- name: Upload data to the website
if: ${{ github.event_name != 'pull_request' }}
env:
@@ -157,16 +172,20 @@ jobs:
persist-credentials: true
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-python@v6.2.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- run: cargo install cargo-criterion
- name: build benchmarks
run: cargo build --release --benches
- name: collect execution benchmark data
run: cargo criterion --bench execution
- name: collect microbenchmarks data
run: cargo criterion --bench microbenchmarks
- name: restructure generated files
run: |
cd ./target/criterion/reports
@@ -179,6 +198,7 @@ jobs:
cd ..
mv reports/* .
rmdir reports
- name: upload benchmark data to the website
if: ${{ github.event_name != 'pull_request' }}
env:

View File

@@ -10,9 +10,6 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
env:
PYTHON_VERSION: "3.14.3"
jobs:
check_deps:
permissions:
@@ -37,46 +34,71 @@ jobs:
# Checkout only Lib/ directory from PR head for accurate comparison
git checkout ${{ github.event.pull_request.head.sha }} -- Lib/
- name: Checkout CPython
- name: Get target CPython version
id: cpython-version
run: |
git clone --depth 1 --branch "v${{ env.PYTHON_VERSION }}" https://github.com/python/cpython.git cpython
version=$(cat .python-version)
echo "version=${version}" >> "$GITHUB_OUTPUT"
- name: Checkout CPython
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: python/cpython
path: cpython
ref: "v${{ steps.cpython-version.outputs.version }}"
fetch-depth: 1
persist-credentials: false
- name: Get changed Lib files
id: changed-files
id: all-changed-files
run: |
# Get the list of changed files under Lib/
changed=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} -- 'Lib/*.py' 'Lib/**/*.py' | head -50)
echo "Changed files:"
echo "$changed"
{
echo 'changed<<EOF'
# Extract unique module names
modules=""
for file in $changed; do
if [[ "$file" == Lib/test/* ]]; then
# Test files: Lib/test/test_pydoc.py -> test_pydoc, Lib/test/test_pydoc/foo.py -> test_pydoc
module=$(echo "$file" | sed -E 's|^Lib/test/||; s|\.py$||; s|/.*||')
# Skip non-test files in test/ (e.g., support.py, __init__.py)
if [[ ! "$module" == test_* ]]; then
git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} -- 'Lib/*.py' 'Lib/**/*.py'
echo 'EOF'
} >> "$GITHUB_OUTPUT"
- name: Parse changed files
id: changed-files
run: |
from os import environ
files = environ["FILES"]
modules = set()
for file in files.splitlines():
file = file.strip()
if file.startswith("Lib/test/"):
# Test files:
# Lib/test/test_pydoc.py -> test_pydoc
# Lib/test/test_pydoc/foo.py -> test_pydoc
module = file.removeprefix("Lib/test/").split("/")[0]
if not module.startswith("test_"):
continue
fi
else
# Lib files: Lib/foo.py -> foo, Lib/foo/__init__.py -> foo
module=$(echo "$file" | sed -E 's|^Lib/||; s|/__init__\.py$||; s|\.py$||; s|/.*||')
fi
if [[ -n "$module" && ! " $modules " =~ " $module " ]]; then
modules="$modules $module"
fi
done
else:
# Lib files:
# Lib/foo.py -> foo
# Lib/foo/__init__.py -> foo
module = file.removeprefix("Lib/").split("/")[0]
module = module.split(".")[0]
modules.add(module)
modules=$(echo "$modules" | xargs) # trim whitespace
echo "Detected modules: $modules"
echo "modules=$modules" >> $GITHUB_OUTPUT
print(f"{modules=}")
output = " ".join(sorted(modules))
output_file = environ["GITHUB_OUTPUT"]
with open(output_file, mode="a", encoding="utf-8") as fd:
fd.write(f"modules={output}\n")
env:
FILES: ${{ steps.all-changed-files.outputs.changed }}
shell: python
- name: Setup Python
if: steps.changed-files.outputs.modules != ''
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "${{ env.PYTHON_VERSION }}"
- name: Run deps check
if: steps.changed-files.outputs.modules != ''
@@ -92,7 +114,7 @@ jobs:
- name: Post comment
if: steps.deps-check.outputs.deps_output != ''
uses: marocchino/sticky-pull-request-comment@v3
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
with:
header: lib-deps-check
number: ${{ github.event.pull_request.number }}
@@ -109,7 +131,7 @@ jobs:
- name: Remove comment if no Lib changes
if: steps.changed-files.outputs.modules == ''
uses: marocchino/sticky-pull-request-comment@v3
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
with:
header: lib-deps-check
number: ${{ github.event.pull_request.number }}

View File

@@ -12,18 +12,19 @@ on:
required: false
default: true
permissions:
contents: write
env:
X86_64_PC_WINDOWS_MSVC_OPENSSL_LIB_DIR: C:\Program Files\OpenSSL\lib\VC\x64\MD
X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR: C:\Program Files\OpenSSL\include
permissions: {}
jobs:
build:
runs-on: ${{ matrix.os }}
# Disable this scheduled job when running on a fork.
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
permissions:
contents: read
strategy:
matrix:
include:
@@ -67,7 +68,7 @@ jobs:
libtool: true
- name: Build RustPython
run: cargo build --release --target=${{ matrix.target }} --verbose --no-default-features --features stdlib,stdio,importlib,encodings,sqlite,host_env,ssl-rustls,threading,jit
run: cargo build --release --target=${{ matrix.target }} --verbose --no-default-features --features stdlib,stdio,importlib,encodings,sqlite,host_env,ssl-rustls-aws-lc,threading,jit
- name: Rename Binary
run: cp target/${{ matrix.target }}/release/rustpython target/rustpython-release-${{ runner.os }}-${{ matrix.target }}
@@ -78,7 +79,7 @@ jobs:
if: runner.os == 'Windows'
- name: Upload Binary Artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: rustpython-release-${{ runner.os }}-${{ matrix.target }}
path: target/rustpython-release-${{ runner.os }}-${{ matrix.target }}*
@@ -87,6 +88,8 @@ jobs:
runs-on: ubuntu-latest
# Disable this scheduled job when running on a fork.
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
@@ -103,7 +106,7 @@ jobs:
run: cp target/wasm32-wasip1/release/rustpython.wasm target/rustpython-release-wasm32-wasip1.wasm
- name: Upload Binary Artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: rustpython-release-wasm32-wasip1
path: target/rustpython-release-wasm32-wasip1.wasm
@@ -111,12 +114,13 @@ jobs:
- name: install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
package-manager-cache: false
- uses: mwilliamson/setup-wabt-action@v3
with: { wabt-version: "1.0.30" }
- uses: mwilliamson/setup-wabt-action@427f2fdd70bc4dbc2e53c2eb4f19f66162d71bd2 # v4.0.0
with:
wabt-version: "1.0.30"
- name: build demo
run: |
@@ -137,7 +141,7 @@ jobs:
- name: Deploy demo to Github Pages
if: ${{ github.repository == 'RustPython/RustPython' }}
uses: peaceiris/actions-gh-pages@v4
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
with:
deploy_key: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
publish_dir: ./wasm/demo/dist
@@ -149,6 +153,8 @@ jobs:
# Disable this scheduled job when running on a fork.
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
needs: [build, build-wasm]
permissions:
contents: write # for creating a release
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
@@ -189,7 +195,7 @@ jobs:
$PRERELEASE_ARG \
bin/rustpython-release-*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ github.token }}
tag: ${{ github.ref_name }}
run: ${{ github.run_number }}
PRE_RELEASE_INPUT: ${{ github.event.inputs.pre-release }}

77
.github/workflows/update-caches.yml vendored Normal file
View File

@@ -0,0 +1,77 @@
name: Update Actions Caches
permissions:
contents: read
on:
workflow_dispatch:
push:
branches:
- main
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
env:
CARGO_INCREMENTAL: 0
CARGO_TERM_COLOR: always
CARGO_PROFILE_TEST_DEBUG: 0
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_RELEASE_DEBUG: 0
CARGO_ARGS: --workspace --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls-aws-lc,host_env,threading,jit --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher
jobs:
build-caches:
name: Build Caches
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: macos-latest
toolchain: stable
target: ""
- os: ubuntu-latest
toolchain: stable
target: ""
- os: windows-latest
toolchain: stable
target: ""
steps:
- name: Checkout RustPython main branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: RustPython/RustPython
ref: main
persist-credentials: false
- name: Setup Rust
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
with:
toolchain: ${{ matrix.toolchain }}
target: ${{ matrix.target }}
- name: Install macos dependencies
uses: ./.github/actions/install-macos-deps
with:
openssl: true
- name: Build dev cache # dev profile used by check & doc
run: cargo build --profile dev ${{ env.CARGO_ARGS }}
- name: Build test cache
run: cargo build --profile test ${{ env.CARGO_ARGS }}
- name: Build release cache
run: cargo build --profile release ${{ env.CARGO_ARGS }}
- name: Save cache
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ matrix.toolchain }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('Cargo.lock') }}-${{ github.sha }}

View File

@@ -43,7 +43,7 @@ jobs:
- name: Generate docs
run: python crates/doc/generate.py
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: doc-db-${{ inputs.python-version }}-${{ matrix.os }}
path: "crates/doc/generated/*.json"
@@ -87,19 +87,18 @@ jobs:
OUTPUT_FILE='crates/doc/src/data.inc.rs'
echo -n '' > $OUTPUT_FILE
# shellcheck disable=SC2016
{
echo '// This file was auto-generated by `.github/workflows/update-doc-db.yml`.'
echo "// CPython version: ${PYTHON_VERSION}"
echo '// spell-checker: disable'
echo ''
echo "pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! {"
cat crates/doc/generated/raw_entries.txt
echo '};'
} > "$OUTPUT_FILE"
echo '// This file was auto-generated by `.github/workflows/update-doc-db.yml`.' >> $OUTPUT_FILE
echo "// CPython version: ${PYTHON_VERSION}" >> $OUTPUT_FILE
echo '// spell-checker: disable' >> $OUTPUT_FILE
echo '' >> $OUTPUT_FILE
echo "pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! {" >> $OUTPUT_FILE
cat crates/doc/generated/raw_entries.txt >> $OUTPUT_FILE
echo '};' >> $OUTPUT_FILE
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: doc-db-${{ inputs.python-version }}
path: "crates/doc/src/data.inc.rs"

View File

@@ -13,7 +13,6 @@ permissions:
issues: write
env:
PYTHON_VERSION: "v3.14.3"
ISSUE_ID: "6839"
jobs:
@@ -29,13 +28,20 @@ jobs:
sparse-checkout: |-
Lib
scripts/update_lib
.python-version
- name: Clone CPython ${{ env.PYTHON_VERSION }}
- name: Get target CPython version
id: cpython-version
run: |
version=$(cat rustpython/.python-version)
echo "version=${version}" >> "$GITHUB_OUTPUT"
- name: Clone CPython
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: python/cpython
path: cpython
ref: ${{ env.PYTHON_VERSION }}
ref: "v${{ steps.cpython-version.outputs.version }}"
persist-credentials: false
sparse-checkout: |
Lib
@@ -56,14 +62,14 @@ jobs:
## Summary
Check \`scripts/update_lib\` for tools. As a note, the current latest Python version is \`${{ env.PYTHON_VERSION }}\`.
Check \`scripts/update_lib\` for tools. As a note, the current latest Python version is \`${{ steps.cpython-version.outputs.version }}\`.
Previous versions' issues as reference
- 3.13: #5529
<!--
Quick guideline for Copilot:
# Clone \`github.com/python/cpython\` \`${{ env.PYTHON_VERSION }}\` tag under RustPython working dir with depth 1 option; never 3.14.0 or 3.14.1 or 3.14.2
# Clone \`github.com/python/cpython\` \`${{ steps.cpython-version.outputs.version }}\` tag under RustPython working dir with depth 1 option; never 3.14.0 or 3.14.1 or 3.14.2
# Pick a library or test to update. Probably user give one.
# Run \`python3 scripts/update_lib quick <name>\`
# A commit is automatically created. push the commit.

View File

@@ -58,11 +58,11 @@ jobs:
comment_repo: ""
steps:
- name: Setup Scripts
uses: github/gh-aw/actions/setup@48d8fdfddc8cad854ac0c70ceb573f09fb8f9c9b # v0.62.5
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
with:
destination: /opt/gh-aw/actions
- name: Check workflow file timestamps
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_WORKFLOW_FILE: "upgrade-pylib.lock.yml"
with:
@@ -99,7 +99,7 @@ jobs:
secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
steps:
- name: Setup Scripts
uses: github/gh-aw/actions/setup@48d8fdfddc8cad854ac0c70ceb573f09fb8f9c9b # v0.62.5
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
with:
destination: /opt/gh-aw/actions
- name: Checkout repository
@@ -114,7 +114,7 @@ jobs:
run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
# Cache configuration from frontmatter processed below
- name: Cache (cpython-lib-${{ env.PYTHON_VERSION }})
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
key: cpython-lib-${{ env.PYTHON_VERSION }}
path: cpython
@@ -135,7 +135,7 @@ jobs:
id: checkout-pr
if: |
github.event.pull_request
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
with:
@@ -147,7 +147,7 @@ jobs:
await main();
- name: Generate agentic run info
id: generate_aw_info
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const fs = require('fs');
@@ -201,7 +201,7 @@ jobs:
run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.16.4
- name: Determine automatic lockdown mode for GitHub MCP server
id: determine-automatic-lockdown
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
@@ -484,7 +484,7 @@ jobs:
}
GH_AW_MCP_CONFIG_EOF
- name: Generate workflow overview
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
@@ -508,10 +508,11 @@ jobs:
cat << 'GH_AW_PROMPT_EOF' > "$GH_AW_PROMPT"
<system>
GH_AW_PROMPT_EOF
cat "/opt/gh-aw/prompts/xpia.md" >> "$GH_AW_PROMPT"
cat "/opt/gh-aw/prompts/temp_folder_prompt.md" >> "$GH_AW_PROMPT"
cat "/opt/gh-aw/prompts/markdown.md" >> "$GH_AW_PROMPT"
cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
{
cat "/opt/gh-aw/prompts/xpia.md"
cat "/opt/gh-aw/prompts/temp_folder_prompt.md"
cat "/opt/gh-aw/prompts/markdown.md"
cat << 'GH_AW_PROMPT_EOF'
<safe-outputs>
<description>GitHub API Access Instructions</description>
<important>
@@ -569,14 +570,15 @@ jobs:
</github-context>
GH_AW_PROMPT_EOF
cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
cat << 'GH_AW_PROMPT_EOF'
</system>
GH_AW_PROMPT_EOF
cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
cat << 'GH_AW_PROMPT_EOF'
{{#runtime-import .github/workflows/upgrade-pylib.md}}
GH_AW_PROMPT_EOF
} >> "$GH_AW_PROMPT"
- name: Substitute placeholders
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_ENV_ISSUE_ID: ${{ env.ISSUE_ID }}
@@ -610,7 +612,7 @@ jobs:
}
});
- name: Interpolate variables and render templates
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_ENV_ISSUE_ID: ${{ env.ISSUE_ID }}
@@ -690,7 +692,7 @@ jobs:
bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
- name: Redact secrets in logs
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
@@ -705,14 +707,14 @@ jobs:
SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Safe Outputs
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: safe-output
path: ${{ env.GH_AW_SAFE_OUTPUTS }}
if-no-files-found: warn
- name: Ingest agent output
id: collect_output
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,index.crates.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,sh.rustup.rs,static.crates.io,static.rust-lang.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
@@ -726,13 +728,13 @@ jobs:
await main();
- name: Upload sanitized agent output
if: always() && env.GH_AW_AGENT_OUTPUT
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: agent-output
path: ${{ env.GH_AW_AGENT_OUTPUT }}
if-no-files-found: warn
- name: Upload engine output files
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: agent_outputs
path: |
@@ -741,7 +743,7 @@ jobs:
if-no-files-found: ignore
- name: Parse agent logs for step summary
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
with:
@@ -752,7 +754,7 @@ jobs:
await main();
- name: Parse MCP gateway logs for step summary
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
@@ -772,7 +774,7 @@ jobs:
- name: Upload agent artifacts
if: always()
continue-on-error: true
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: agent-artifacts
path: |
@@ -804,7 +806,7 @@ jobs:
total_count: ${{ steps.missing_tool.outputs.total_count }}
steps:
- name: Setup Scripts
uses: github/gh-aw/actions/setup@48d8fdfddc8cad854ac0c70ceb573f09fb8f9c9b # v0.62.5
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
with:
destination: /opt/gh-aw/actions
- name: Download agent output artifact
@@ -820,7 +822,7 @@ jobs:
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
- name: Process No-Op Messages
id: noop
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_NOOP_MAX: 1
@@ -834,7 +836,7 @@ jobs:
await main();
- name: Record Missing Tool
id: missing_tool
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
@@ -847,7 +849,7 @@ jobs:
await main();
- name: Handle Agent Failure
id: handle_agent_failure
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
@@ -865,7 +867,7 @@ jobs:
await main();
- name: Handle No-Op Message
id: handle_noop_message
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
@@ -882,7 +884,7 @@ jobs:
await main();
- name: Handle Create Pull Request Error
id: handle_create_pr_error
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
@@ -896,7 +898,7 @@ jobs:
await main();
- name: Update reaction comment with completion status
id: conclusion
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
@@ -925,7 +927,7 @@ jobs:
success: ${{ steps.parse_results.outputs.success }}
steps:
- name: Setup Scripts
uses: github/gh-aw/actions/setup@48d8fdfddc8cad854ac0c70ceb573f09fb8f9c9b # v0.62.5
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
with:
destination: /opt/gh-aw/actions
- name: Download agent artifacts
@@ -946,7 +948,7 @@ jobs:
run: |
echo "Agent output-types: $AGENT_OUTPUT_TYPES"
- name: Setup threat detection
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
WORKFLOW_NAME: "Upgrade Python Library"
WORKFLOW_DESCRIPTION: "Pick an out-of-sync Python library from the todo list and upgrade it\nby running `scripts/update_lib quick`, then open a pull request."
@@ -999,7 +1001,7 @@ jobs:
XDG_CONFIG_HOME: /home/runner
- name: Parse threat detection results
id: parse_results
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
@@ -1008,7 +1010,7 @@ jobs:
await main();
- name: Upload threat detection log
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: threat-detection.log
path: /tmp/gh-aw/threat-detection/detection.log
@@ -1037,7 +1039,7 @@ jobs:
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
uses: github/gh-aw/actions/setup@48d8fdfddc8cad854ac0c70ceb573f09fb8f9c9b # v0.62.5
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
with:
destination: /opt/gh-aw/actions
- name: Download agent output artifact
@@ -1079,7 +1081,7 @@ jobs:
echo "Git configured with standard GitHub Actions identity"
- name: Process Safe Outputs
id: process_safe_outputs
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"${{ github.ref_name }}\",\"draft\":false,\"expires\":30,\"labels\":[\"pylib-sync\"],\"max\":1,\"max_patch_size\":1024,\"title_prefix\":\"Update \"},\"missing_data\":{},\"missing_tool\":{}}"

View File

@@ -52,7 +52,7 @@ cache:
- cpython-lib-
env:
PYTHON_VERSION: "v3.14.3"
PYTHON_VERSION: "v3.14.4"
ISSUE_ID: "6839"
---

14
.github/zizmor.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
rules:
unpinned-uses:
config:
policies:
# dtolnay/rust-toolchain is a trusted action that uses lightweight branch
# refs (@stable, @nightly, etc.) by design. Pinning to a hash would break
# the intended usage pattern.
# We can remove this once https://github.com/dtolnay/rust-toolchain/issues/180 is resolved
dtolnay/rust-toolchain: any
# dtolnay/rust-toolchain handles component installation, target addition, and
# override configuration beyond what a bare `rustup` invocation provides.
# See: https://github.com/zizmorcore/zizmor/issues/1817
superfluous-actions:
disable: true

3
.gitignore vendored
View File

@@ -10,7 +10,6 @@ __pycache__/
wasm-pack.log
.idea/
.envrc
.python-version
flame-graph.html
flame.txt
@@ -28,4 +27,4 @@ Lib/site-packages/*
Lib/test/data/*
!Lib/test/data/README
cpython/
.claude/scheduled_tasks.lock

View File

@@ -10,7 +10,7 @@ repos:
priority: 0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.7
rev: v0.15.12
hooks:
- id: ruff-format
priority: 0
@@ -40,17 +40,30 @@ repos:
types: [rust]
priority: 0
- id: generate-opcode-metadata
name: generate opcode metadata
entry: python scripts/generate_opcode_metadata.py
files: '^(crates/compiler-core/src/bytecode/instruction\.rs|scripts/generate_opcode_metadata\.py)$'
- id: generate-rs-opcode-metadata
name: generate rust opcode metadata
entry: python tools/opcode_metadata/generate_rs_opcode_metadata.py
files: '^(crates/compiler-core/src/bytecode/instruction\.rs|tools/opcode_metadata/*)$'
pass_filenames: false
language: system
require_serial: true
priority: 1 # so rustfmt runs first
stages:
- manual
- id: generate-py-opcode-metadata
name: generate python opcode metadata
entry: python tools/opcode_metadata/generate_py_opcode_metadata.py
files: '^(crates/compiler-core/src/bytecode/instruction\.rs|tools/opcode_metadata/*)$'
pass_filenames: false
language: system
require_serial: true
priority: 1 # so rustfmt runs first
stages:
- manual
- repo: https://github.com/streetsidesoftware/cspell-cli
rev: v9.7.0
rev: v10.0.0
hooks:
- id: cspell
types: [rust]
@@ -64,7 +77,7 @@ repos:
priority: 0
- repo: https://github.com/rbubley/mirrors-prettier
rev: v3.8.1
rev: v3.8.3
hooks:
- id: prettier
files: '^wasm/.*$'

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.14.5

View File

@@ -38,6 +38,12 @@ RustPython is a Python 3 interpreter written in Rust, implementing Python 3.14.0
- Always ask the user before performing any git operations that affect the remote repository
- Commits can be created locally when requested, but pushing and PR creation require explicit approval
**CRITICAL: Pre-commit Checks**
- Before creating ANY commit, you MUST run `prek run --all-files` (or `pre-commit run --all-files`) AND the full test suite. Both must pass — do not commit if either fails.
- Test commands are documented in the [Testing](#testing) section below. At minimum run `cargo test --workspace --exclude rustpython_wasm --exclude rustpython-venvlauncher`; if the change touches `extra_tests/snippets/` run `pytest -v` there too, and if it touches `Lib/` or interpreter behavior, run the relevant `cargo run --release -- -m test <module>` modules.
- If a hook auto-fixes files (e.g. `ruff-format`, `rustfmt`), re-stage the fixes, re-run `prek` until it reports a clean pass, then re-run the tests, then commit.
- NEVER bypass these checks with `--no-verify`, `--no-gpg-sign`, or by skipping tests "because the change is small". If a hook or test fails, fix the underlying issue and create a new commit — do not amend or force the failing commit through.
## Important Development Notes
### Running Python Code
@@ -81,6 +87,35 @@ The `Lib/` directory contains Python standard library files copied from the CPyt
- `unittest.skip("TODO: RustPython <reason>")`
- `unittest.expectedFailure` with `# TODO: RUSTPYTHON <reason>` comment
#### Choosing the right marker
When marking a test that fails on RustPython, prefer one of the following forms:
```python
@unittest.expectedFailure # TODO: RUSTPYTHON; <reason>
# or
@unittest.expectedFailureIf(<condition>, "TODO: RUSTPYTHON; <reason>")
```
If the test would crash the interpreter (segfault, Rust panic, abort, infinite loop), use `skip` instead so the rest of the suite can still run:
```python
@unittest.skip("TODO: RUSTPYTHON; <reason>")
# or
@unittest.skipIf(<condition>, "TODO: RUSTPYTHON; <reason>")
```
**When to use which:**
- **Prefer `expectedFailure` / `expectedFailureIf`** by default. The test body still runs, so if RustPython is later fixed, the unexpected pass surfaces immediately and the decorator can be removed. Use the conditional `*If` form when the failure is environment-specific (e.g., a platform or build flag).
- **Use `skip` / `skipIf` only when running the test would take down the test process** — segfaults, Rust panics, aborts, or hangs that block subsequent tests. Skipping keeps the suite usable; `expectedFailure` cannot help here, because the test body still executes.
To find WIP entries that are partly modified and may need follow-up:
```bash
grep -d recurse 'TODO: RUSTPYTHON' Lib/test/
```
### Clean Build
When you modify bytecode instructions, a full clean is required:
@@ -129,6 +164,7 @@ Run `./scripts/whats_left.py` to get a list of unimplemented methods, which is h
- Do not delete or rewrite existing comments unless they are factually wrong or directly contradict the new code.
- Do not add decorative section separators (e.g. `// -----------`, `// ===`, `/* *** */`). Use `///` doc-comments or short `//` comments only when they add value.
- Do not put `///` doc comments on items annotated with `#[pyattr]`, `#[pyclass]`, or `#[pyfunction]`. The derive macros pull authoritative docstrings from CPython via the `rustpython-doc` crate; a Rust doc comment overrides that source, and on `#[pyattr]` it is silently dropped.
#### Avoid Duplicate Code in Branches
@@ -258,9 +294,14 @@ See DEVELOPMENT.md "CPython Version Upgrade Checklist" section.
- Document that it requires PEP 695 support
- Focus on tests that can be fixed through Rust code changes only
## CI Workflows
If you modify any file under `.github/workflows/`, the change must pass a [zizmor](https://docs.zizmor.sh/) scan in CI.
## Documentation
- Check the [architecture document](/architecture/architecture.md) for a high-level overview
- Read the [development guide](/DEVELOPMENT.md) for detailed setup instructions
- Generate documentation with `cargo doc --no-deps --all`
- Online documentation is available at [docs.rs/rustpython](https://docs.rs/rustpython/)
- [How to update test files](https://github.com/RustPython/RustPython/wiki/How-to-update-test-files#checkout-cpython-source-code-initial-setup) — guide for syncing test cases from upstream CPython into the `Lib/` directory

2074
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,8 @@ repository.workspace = true
license.workspace = true
[features]
default = ["threading", "stdlib", "stdio", "importlib", "ssl-rustls", "host_env"]
capi = ["dep:rustpython-capi", "threading"]
default = ["threading", "stdlib", "stdio", "importlib", "ssl-rustls-aws-lc", "host_env"]
host_env = ["rustpython-vm/host_env", "rustpython-stdlib?/host_env"]
importlib = ["rustpython-vm/importlib"]
encodings = ["rustpython-vm/encodings"]
@@ -21,8 +22,10 @@ freeze-stdlib = ["stdlib", "rustpython-vm/freeze-stdlib", "rustpython-pylib?/fre
jit = ["rustpython-vm/jit"]
threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"]
sqlite = ["rustpython-stdlib/sqlite"]
ssl = []
ssl = ["host_env"]
ssl-rustls = ["ssl", "rustpython-stdlib/ssl-rustls"]
ssl-rustls-aws-lc = ["ssl-rustls", "dep:rustls", "rustls/aws_lc_rs"]
ssl-rustls-aws-lc-fips = ["ssl-rustls-aws-lc", "rustls/fips"]
ssl-openssl = ["ssl", "rustpython-stdlib/ssl-openssl"]
ssl-vendor = ["ssl-openssl", "rustpython-stdlib/ssl-vendor"]
tkinter = ["rustpython-stdlib/tkinter"]
@@ -31,20 +34,23 @@ tkinter = ["rustpython-stdlib/tkinter"]
winresource = "0.1"
[dependencies]
rustpython-capi = { workspace = true, optional = true }
rustpython-compiler = { workspace = true }
rustpython-pylib = { workspace = true, optional = true }
rustpython-stdlib = { workspace = true, optional = true, features = ["compiler"] }
rustpython-vm = { workspace = true, features = ["compiler", "gc"] }
cfg-if = { workspace = true }
log = { workspace = true }
flame = { workspace = true, optional = true }
lexopt = "0.3"
dirs = { package = "dirs-next", version = "2.0" }
dirs = "6"
env_logger = "0.11"
flamescope = { version = "0.1.2", optional = true }
rustls = { workspace = true, optional = true }
rustls-graviola = { workspace = true, optional = true }
[target.'cfg(windows)'.dependencies]
libc = { workspace = true }
@@ -53,7 +59,7 @@ rustyline = { workspace = true }
[dev-dependencies]
criterion = { workspace = true }
pyo3 = { version = "0.28.2", features = ["auto-initialize"] }
pyo3 = { workspace = true, features = ["auto-initialize"] }
rustpython-stdlib = { workspace = true }
ruff_python_parser = { workspace = true }
@@ -69,6 +75,17 @@ harness = false
name = "rustpython"
path = "src/main.rs"
[[example]]
name = "custom_tls_providers"
path = "examples/custom_tls_providers.rs"
required-features = [
"rustls-graviola",
"rustls/ring",
"rustpython-pylib/freeze-stdlib",
"rustpython-stdlib/ssl-rustls",
"rustpython-vm/freeze-stdlib",
]
[profile.dev.package."*"]
opt-level = 3
@@ -136,15 +153,17 @@ exclude = ["pymath"]
version = "0.5.0"
authors = ["RustPython Team"]
edition = "2024"
rust-version = "1.93.0"
rust-version = "1.95.0"
repository = "https://github.com/RustPython/RustPython"
license = "MIT"
[workspace.dependencies]
rustpython-capi = { path = "crates/capi", version = "0.5.0" }
rustpython-compiler-core = { path = "crates/compiler-core", version = "0.5.0" }
rustpython-compiler = { path = "crates/compiler", version = "0.5.0" }
rustpython-codegen = { path = "crates/codegen", version = "0.5.0" }
rustpython-common = { path = "crates/common", version = "0.5.0" }
rustpython-host_env = { path = "crates/host_env", version = "0.5.0" }
rustpython-derive = { path = "crates/derive", version = "0.5.0" }
rustpython-derive-impl = { path = "crates/derive-impl", version = "0.5.0" }
rustpython-jit = { path = "crates/jit", version = "0.5.0" }
@@ -156,77 +175,154 @@ rustpython-sre_engine = { path = "crates/sre_engine", version = "0.5.0" }
rustpython-wtf8 = { path = "crates/wtf8", version = "0.5.0" }
rustpython-doc = { path = "crates/doc", version = "0.5.0" }
# Ruff tag 0.15.6 is based on commit e4c7f357777a2fdd34dbe6a98b1b7d3e7488f675
# Use RustPython-packaged Ruff crates from the published fork while keeping
# existing crate names in the codebase.
ruff_python_parser = { package = "rustpython-ruff_python_parser", version = "0.15.8" }
ruff_python_ast = { package = "rustpython-ruff_python_ast", version = "0.15.8" }
ruff_text_size = { package = "rustpython-ruff_text_size", version = "0.15.8" }
ruff_source_file = { package = "rustpython-ruff_source_file", version = "0.15.8" }
# To update ruff crates, comment out the above lines and uncomment the following lines to pull directly from the Ruff repository at the specified commit hash.
# Ruff tag 0.15.8 is based on commit c2a8815842f9dc5d24ec19385eae0f1a7188b0d9
# at the time of this capture. We use the commit hash to ensure reproducible builds.
ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", rev = "e4c7f357777a2fdd34dbe6a98b1b7d3e7488f675" }
ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", rev = "e4c7f357777a2fdd34dbe6a98b1b7d3e7488f675" }
ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", rev = "e4c7f357777a2fdd34dbe6a98b1b7d3e7488f675" }
ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", rev = "e4c7f357777a2fdd34dbe6a98b1b7d3e7488f675" }
# ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
# ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
# ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
# ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
der = { version = "0.8", features = ["alloc", "oid", "pem", "zeroize"] }
phf = { version = "0.13.1", default-features = false, features = ["macros"]}
ahash = "0.8.12"
adler32 = "1.2.0"
approx = "0.5.1"
ascii = "1.1"
base64 = "0.22"
blake2 = "0.10.4"
bitflags = "2.11.0"
bitflagset = "0.0.3"
bstr = "1"
cfg-if = "1.0"
chrono = { version = "0.4.44", default-features = false, features = ["clock", "oldtime", "std"] }
bzip2 = "0.6"
chrono = { version = "0.4.44", default-features = false, features = ["clock", "std"] }
console_error_panic_hook = "0.1"
constant_time_eq = "0.4"
cranelift = "0.131.2"
cranelift-jit = "0.131.2"
cranelift-module = "0.131.0"
crc32fast = "1.3.2"
criterion = { version = "0.8", features = ["html_reports"] }
crossbeam-utils = "0.8.21"
csv-core = "0.1.11"
digest = "0.11.3"
dns-lookup = "3.0"
dyn-clone = "1.0.10"
exitcode = "1.1.2"
flame = "0.2.2"
flamer = "0.5"
flate2 = { version = "1.1.9", default-features = false }
foreign-types-shared = "0.1"
gethostname = "1.0.2"
getrandom = { version = "0.3", features = ["std"] }
glob = "0.3"
half = "2"
hex = "0.4.3"
indexmap = { version = "2.13.0", features = ["std"] }
insta = "1.46"
hexf-parse = "0.2.1"
hmac = "0.13"
indexmap = { version = "2.14.0", features = ["std"] }
insta = "1.47"
itertools = "0.14.0"
is-macro = "0.3.7"
js-sys = "0.3"
junction = "1.4.2"
libc = "0.2.183"
lexical-parse-float = "1.0.6"
libc = "0.2.186"
libffi = "5"
libloading = "0.9"
liblzma = "0.4"
liblzma-sys = "0.4"
libsqlite3-sys = "0.37"
libz-rs-sys = "0.6"
lock_api = "0.4"
log = "0.4.29"
nix = { version = "0.30", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] }
lz4_flex = "0.13"
nix = { version = "0.31", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] }
mac_address = "1.1.3"
malachite-bigint = "0.9.1"
malachite-q = "0.9.1"
malachite-base = "0.9.1"
md-5 = "0.11.0"
memchr = "2.8.0"
memmap2 = "0.9.10"
mt19937 = "<=3.2" # upgrade it once rand is upgraded
num-complex = "0.4.6"
num-integer = "0.1.46"
num-traits = "0.2"
num_cpus = "1.17.0"
num_enum = { version = "0.7", default-features = false }
oid-registry = "0.8"
openssl = "0.10.80"
openssl-sys = "0.9.110"
openssl-probe = "0.2.1"
optional = "0.5"
parking_lot = "0.12.3"
paste = "1.0.15"
pbkdf2 = "0.13"
pem-rfc7468 = "1.0"
pkcs8 = "0.11"
proc-macro2 = "1.0.105"
psm = "0.1"
pymath = { version = "0.2.0", features = ["mul_add", "malachite-bigint", "complex"] }
pyo3 = "0.28"
quote = "1.0.45"
radium = "1.1.1"
rand = "0.9"
rand_core = { version = "0.9", features = ["os_rng"] }
rustix = { version = "1.1", features = ["event"] }
rustyline = "17.0.1"
rapidhash = "4.4.1"
result-like = "0.5.0"
rustix = { version = "1.1", features = ["event", "param", "system"] }
rustls = { version = "0.23.39", default-features = false }
rustls-graviola = "0.3"
rustls-native-certs = "0.8"
rustls-pemfile = "2.2"
rustls-platform-verifier = "0.7"
rustyline = "18"
serde = { package = "serde_core", version = "1.0.225", default-features = false, features = ["alloc"] }
schannel = "0.1.28"
scoped-tls = "1"
schannel = "0.1.29"
scopeguard = "1"
serde-wasm-bindgen = "0.6.5"
sha-1 = "0.10.0"
sha2 = "0.11.0"
sha3 = "0.12.0"
siphasher = "1"
socket2 = "0.6.3"
static_assertions = "1.1"
strum = "0.28"
strum_macros = "0.28"
syn = "2"
syn-ext = "0.5.0"
system-configuration = "0.7.0"
tcl-sys = { git = "https://github.com/arihant2math/tkinter.git", tag = "v0.2.0" }
textwrap = { version = "0.16.2", default-features = false }
termios = "0.3.3"
thiserror = "2.0"
unicode-casing = "0.1.1"
unic-char-property = "0.9.0"
unic-normal = "0.9.0"
timsort = "0.1.2"
tk-sys = { git = "https://github.com/arihant2math/tkinter.git", tag = "v0.2.0" }
icu_casemap = "2"
icu_locale = "2"
icu_properties = "2"
icu_normalizer = "2"
uuid = "1.23.1"
ucd = "0.1.1"
unic-ucd-age = "0.9.0"
unic-ucd-bidi = "0.9.0"
unic-ucd-category = "0.9.0"
unic-ucd-ident = "0.9.0"
unicode_names2 = "2.0.0"
unicode-bidi-mirroring = "0.4"
widestring = "1.2.0"
windows-sys = "0.61.2"
wasm-bindgen = "0.2.106"
wasm-bindgen-futures = "0.4"
web-sys = "0.3"
webpki-roots = "1.0"
which = "8"
x509-cert = "0.2.5"
x509-parser = "0.18"
xml = "1.2"
writeable = "0.6"
# Lints
@@ -234,13 +330,61 @@ wasm-bindgen = "0.2.106"
unsafe_code = "allow"
unsafe_op_in_unsafe_fn = "deny"
elided_lifetimes_in_paths = "warn"
unreachable_pub = "warn"
[workspace.lints.clippy]
correctness = { level = "warn", priority = -2 }
suspicious = { level = "warn", priority = -2 }
perf = { level = "warn", priority = -2 }
style = { level = "warn", priority = -2 }
complexity = { level = "warn", priority = -2 }
# pedantic = { level = "warn", priority = -2 } # TODO: Enable this
missing_errors_doc = "allow" # Too many errors. No auto-fix available
missing_panics_doc = "allow" # Too many errors. No auto-fix available
match_same_arms = "allow" # Not always more readable
if_not_else = "allow" # Not always more readable
single_match_else = "allow"
similar_names = "allow"
# restriction lints
alloc_instead_of_core = "warn"
cfg_not_test = "warn"
redundant_test_prefix = "warn"
std_instead_of_alloc = "warn"
std_instead_of_core = "warn"
perf = "warn"
style = "warn"
complexity = "warn"
suspicious = "warn"
correctness = "warn"
tests_outside_test_module = "warn"
# nursery lints to enforce gradually
debug_assert_with_mut_call = "warn"
derive_partial_eq_without_eq = "warn"
imprecise_flops = "warn"
or_fun_call = "warn"
redundant_clone = "warn"
search_is_some = "warn"
single_option_map = "warn"
trait_duplication_in_bounds = "warn"
unused_peekable = "warn"
unused_rounding = "warn"
use_self = "warn"
useless_let_if_seq = "warn"
# pedantic lints to enforce gradually
cloned_instead_of_copied = "warn"
collapsible_else_if = "warn"
comparison_chain = "warn"
explicit_into_iter_loop = "warn"
explicit_iter_loop = "warn"
filter_map_next = "warn"
flat_map_option = "warn"
format_collect = "warn"
from_iter_instead_of_collect = "warn"
inconsistent_struct_constructor = "warn"
inefficient_to_string = "warn"
manual_is_variant_and = "warn"
map_unwrap_or = "warn"
must_use_candidate = "warn"
redundant_else = "warn"
uninlined_format_args = "warn"
unnecessary_wraps = "warn"
unnested_or_patterns = "warn"

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 RustPython Team
Copyright (c) 2026 RustPython Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -168,6 +168,13 @@ class Logcat:
# message.
message = message.replace(b"\x00", b"\xc0\x80")
# On API level 30 and higher, Logcat will strip any number of leading
# newlines. This is visible in all `logcat` modes, even --binary. Work
# around this by adding a leading space, which shouldn't make any
# difference to the log's usability.
if message.startswith(b"\n"):
message = b" " + message
with self._lock:
now = time()
self._bucket_level += (

2
Lib/_opcode_metadata.py generated vendored
View File

@@ -1,4 +1,4 @@
# This file is generated by scripts/generate_opcode_metadata.py
# This file is generated by tools/opcode_metadata/generate_py_opcode_metadata.py
# for RustPython bytecode format (CPython 3.14 compatible opcode numbers).
# Do not edit!

55
Lib/annotationlib.py vendored
View File

@@ -47,6 +47,7 @@ _SLOTS = (
"__cell__",
"__owner__",
"__stringifier_dict__",
"__resolved_str_cache__",
)
@@ -94,6 +95,7 @@ class ForwardRef:
# value later.
self.__code__ = None
self.__ast_node__ = None
self.__resolved_str_cache__ = None
def __init_subclass__(cls, /, *args, **kwds):
raise TypeError("Cannot subclass ForwardRef")
@@ -113,7 +115,7 @@ class ForwardRef:
"""
match format:
case Format.STRING:
return self.__forward_arg__
return self.__resolved_str__
case Format.VALUE:
is_forwardref_format = False
case Format.FORWARDREF:
@@ -258,6 +260,24 @@ class ForwardRef:
"Attempted to access '__forward_arg__' on an uninitialized ForwardRef"
)
@property
def __resolved_str__(self):
# __forward_arg__ with any names from __extra_names__ replaced
# with the type_repr of the value they represent
if self.__resolved_str_cache__ is None:
resolved_str = self.__forward_arg__
names = self.__extra_names__
if names:
visitor = _ExtraNameFixer(names)
ast_expr = ast.parse(resolved_str, mode="eval").body
node = visitor.visit(ast_expr)
resolved_str = ast.unparse(node)
self.__resolved_str_cache__ = resolved_str
return self.__resolved_str_cache__
@property
def __forward_code__(self):
if self.__code__ is not None:
@@ -321,7 +341,7 @@ class ForwardRef:
extra.append(", is_class=True")
if self.__owner__ is not None:
extra.append(f", owner={self.__owner__!r}")
return f"ForwardRef({self.__forward_arg__!r}{''.join(extra)})"
return f"ForwardRef({self.__resolved_str__!r}{''.join(extra)})"
_Template = type(t"")
@@ -357,6 +377,7 @@ class _Stringifier:
self.__cell__ = cell
self.__owner__ = owner
self.__stringifier_dict__ = stringifier_dict
self.__resolved_str_cache__ = None # Needed for ForwardRef
def __convert_to_ast(self, other):
if isinstance(other, _Stringifier):
@@ -919,7 +940,7 @@ def get_annotations(
does not exist, the __annotate__ function is called. The
FORWARDREF format uses __annotations__ if it exists and can be
evaluated, and otherwise falls back to calling the __annotate__ function.
The SOURCE format tries __annotate__ first, and falls back to
The STRING format tries __annotate__ first, and falls back to
using __annotations__, stringified using annotations_to_string().
This function handles several details for you:
@@ -1037,13 +1058,26 @@ def get_annotations(
obj_globals = obj_locals = unwrap = None
if unwrap is not None:
# Use an id-based visited set to detect cycles in the __wrapped__
# and functools.partial.func chain (e.g. f.__wrapped__ = f).
# On cycle detection we stop and use whatever __globals__ we have
# found so far, mirroring the approach of inspect.unwrap().
_seen_ids = {id(unwrap)}
while True:
if hasattr(unwrap, "__wrapped__"):
unwrap = unwrap.__wrapped__
candidate = unwrap.__wrapped__
if id(candidate) in _seen_ids:
break
_seen_ids.add(id(candidate))
unwrap = candidate
continue
if functools := sys.modules.get("functools"):
if isinstance(unwrap, functools.partial):
unwrap = unwrap.func
candidate = unwrap.func
if id(candidate) in _seen_ids:
break
_seen_ids.add(id(candidate))
unwrap = candidate
continue
break
if hasattr(unwrap, "__globals__"):
@@ -1150,3 +1184,14 @@ def _get_dunder_annotations(obj):
if not isinstance(ann, dict):
raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
return ann
class _ExtraNameFixer(ast.NodeTransformer):
"""Fixer for __extra_names__ items in ForwardRef __repr__ and string evaluation"""
def __init__(self, extra_names):
self.extra_names = extra_names
def visit_Name(self, node: ast.Name):
if (new_name := self.extra_names.get(node.id, _sentinel)) is not _sentinel:
node = ast.Name(id=type_repr(new_name))
return node

12
Lib/argparse.py vendored
View File

@@ -149,6 +149,10 @@ def _copy_items(items):
return copy.copy(items)
def _identity(value):
return value
# ===============
# Formatting Help
# ===============
@@ -200,7 +204,7 @@ class HelpFormatter(object):
self._decolor = decolor
else:
self._theme = get_theme(force_no_color=True).argparse
self._decolor = lambda text: text
self._decolor = _identity
# ===============================
# Section and indentation methods
@@ -1903,9 +1907,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
self._subparsers = None
# register types
def identity(string):
return string
self.register('type', None, identity)
self.register('type', None, _identity)
# add help argument if necessary
# (using explicit default to override global argument_default)
@@ -2676,7 +2678,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
if value not in choices:
args = {'value': str(value),
'choices': ', '.join(map(str, action.choices))}
'choices': ', '.join(repr(str(choice)) for choice in action.choices)}
msg = _('invalid choice: %(value)r (choose from %(choices)s)')
if self.suggest_on_error and isinstance(value, str):

View File

@@ -17,6 +17,10 @@ class defaultdict(dict):
val = self.default_factory()
else:
raise KeyError(key)
# CPython parity: a recursive __missing__ via factory() may have
# already populated key; preserve that value instead of overwriting.
if key in self:
return self[key]
self[key] = val
return val

17
Lib/configparser.py vendored
View File

@@ -315,12 +315,15 @@ class ParsingError(Error):
def append(self, lineno, line):
self.errors.append((lineno, line))
self.message += '\n\t[line %2d]: %s' % (lineno, repr(line))
self.message += f'\n\t[line {lineno:2d}]: {line!r}'
def combine(self, others):
messages = [self.message]
for other in others:
for error in other.errors:
self.append(*error)
for lineno, line in other.errors:
self.errors.append((lineno, line))
messages.append(f'\n\t[line {lineno:2d}]: {line!r}')
self.message = "".join(messages)
return self
@staticmethod
@@ -613,7 +616,9 @@ class RawConfigParser(MutableMapping):
\] # ]
"""
_OPT_TMPL = r"""
(?P<option>.*?) # very permissive!
(?P<option> # very permissive!
(?:(?!{delim})\S)* # non-delimiter non-whitespace
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
\s*(?P<vi>{delim})\s* # any number of space/tab,
# followed by any of the
# allowed delimiters,
@@ -621,7 +626,9 @@ class RawConfigParser(MutableMapping):
(?P<value>.*)$ # everything up to eol
"""
_OPT_NV_TMPL = r"""
(?P<option>.*?) # very permissive!
(?P<option> # very permissive!
(?:(?!{delim})\S)* # non-delimiter non-whitespace
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
\s*(?: # any number of space/tab,
(?P<vi>{delim})\s* # optionally followed by
# any of the allowed

View File

@@ -470,6 +470,8 @@ class CDLL(object):
if name and name.endswith(")") and ".a(" in name:
mode |= _os.RTLD_MEMBER | _os.RTLD_NOW
self._name = name
if handle is not None:
return handle
return _dlopen(name, mode)
def __repr__(self):

26
Lib/ctypes/util.py vendored
View File

@@ -85,15 +85,10 @@ if os.name == "nt":
wintypes.DWORD,
)
_psapi = ctypes.WinDLL('psapi', use_last_error=True)
_enum_process_modules = _psapi["EnumProcessModules"]
_enum_process_modules.restype = wintypes.BOOL
_enum_process_modules.argtypes = (
wintypes.HANDLE,
ctypes.POINTER(wintypes.HMODULE),
wintypes.DWORD,
wintypes.LPDWORD,
)
# gh-145307: We defer loading psapi.dll until _get_module_handles is called.
# Loading additional DLLs at startup for functionality that may never be
# used is wasteful.
_enum_process_modules = None
def _get_module_filename(module: wintypes.HMODULE):
name = (wintypes.WCHAR * 32767)() # UNICODE_STRING_MAX_CHARS
@@ -101,8 +96,19 @@ if os.name == "nt":
return name.value
return None
def _get_module_handles():
global _enum_process_modules
if _enum_process_modules is None:
_psapi = ctypes.WinDLL('psapi', use_last_error=True)
_enum_process_modules = _psapi["EnumProcessModules"]
_enum_process_modules.restype = wintypes.BOOL
_enum_process_modules.argtypes = (
wintypes.HANDLE,
ctypes.POINTER(wintypes.HMODULE),
wintypes.DWORD,
wintypes.LPDWORD,
)
process = _get_current_process()
space_needed = wintypes.DWORD()
n = 1024

26
Lib/dataclasses.py vendored
View File

@@ -725,10 +725,10 @@ def _init_fn(fields, std_fields, kw_only_fields, frozen, has_post_init,
annotation_fields=annotation_fields)
def _frozen_get_del_attr(cls, fields, func_builder):
locals = {'cls': cls,
def _frozen_set_del_attr(cls, fields, func_builder):
locals = {'__class__': cls,
'FrozenInstanceError': FrozenInstanceError}
condition = 'type(self) is cls'
condition = 'type(self) is __class__'
if fields:
condition += ' or name in {' + ', '.join(repr(f.name) for f in fields) + '}'
@@ -736,14 +736,14 @@ def _frozen_get_del_attr(cls, fields, func_builder):
('self', 'name', 'value'),
(f' if {condition}:',
' raise FrozenInstanceError(f"cannot assign to field {name!r}")',
f' super(cls, self).__setattr__(name, value)'),
f' super(__class__, self).__setattr__(name, value)'),
locals=locals,
overwrite_error=True)
func_builder.add_fn('__delattr__',
('self', 'name'),
(f' if {condition}:',
' raise FrozenInstanceError(f"cannot delete field {name!r}")',
f' super(cls, self).__delattr__(name)'),
f' super(__class__, self).__delattr__(name)'),
locals=locals,
overwrite_error=True)
@@ -1199,7 +1199,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
overwrite_error='Consider using functools.total_ordering')
if frozen:
_frozen_get_del_attr(cls, field_list, func_builder)
_frozen_set_del_attr(cls, field_list, func_builder)
# Decide if/how we're going to create a hash function.
hash_action = _hash_action[bool(unsafe_hash),
@@ -1292,10 +1292,18 @@ def _update_func_cell_for__class__(f, oldcls, newcls):
# This function doesn't reference __class__, so nothing to do.
return False
# Fix the cell to point to the new class, if it's already pointing
# at the old class. I'm not convinced that the "is oldcls" test
# is needed, but other than performance can't hurt.
# at the old class.
closure = f.__closure__[idx]
if closure.cell_contents is oldcls:
try:
contents = closure.cell_contents
except ValueError:
# Cell is empty
return False
# This check makes it so we avoid updating an incorrect cell if the
# class body contains a function that was defined in a different class.
if contents is oldcls:
closure.cell_contents = newcls
return True
return False

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2007 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org

View File

@@ -219,7 +219,7 @@ def encode(string, charset='utf-8', encoding=None, lang=''):
"""
if charset == 'unknown-8bit':
bstring = string.encode('ascii', 'surrogateescape')
bstring = string.encode('utf-8', 'surrogateescape')
else:
bstring = string.encode(charset)
if encoding is None:

View File

@@ -80,7 +80,8 @@ from email import utils
# Useful constants and functions
#
WSP = set(' \t')
_WSP = ' \t'
WSP = set(_WSP)
CFWS_LEADER = WSP | set('(')
SPECIALS = set(r'()<>@,:;.\"[]')
ATOM_ENDS = SPECIALS | WSP
@@ -101,6 +102,12 @@ def make_quoted_pairs(value):
return str(value).replace('\\', '\\\\').replace('"', '\\"')
def make_parenthesis_pairs(value):
"""Escape parenthesis and backslash for use within a comment."""
return str(value).replace('\\', '\\\\') \
.replace('(', '\\(').replace(')', '\\)')
def quote_string(value):
escaped = make_quoted_pairs(value)
return f'"{escaped}"'
@@ -632,11 +639,11 @@ class LocalPart(TokenList):
for tok in self[0] + [DOT]:
if tok.token_type == 'cfws':
continue
if (last_is_tl and tok.token_type == 'dot' and
if (last_is_tl and tok.token_type == 'dot' and last and
last[-1].token_type == 'cfws'):
res[-1] = TokenList(last[:-1])
is_tl = isinstance(tok, TokenList)
if (is_tl and last.token_type == 'dot' and
if (is_tl and last.token_type == 'dot' and tok and
tok[0].token_type == 'cfws'):
res.append(TokenList(tok[1:]))
else:
@@ -874,6 +881,12 @@ class MessageID(MsgID):
class InvalidMessageID(MessageID):
token_type = 'invalid-message-id'
class MessageIDList(TokenList):
token_type = 'message-id-list'
@property
def message_ids(self):
return [x for x in self if x.token_type=='msg-id']
class Header(TokenList):
token_type = 'header'
@@ -933,7 +946,7 @@ class WhiteSpaceTerminal(Terminal):
return ' '
def startswith_fws(self):
return True
return self and self[0] in WSP
class ValueTerminal(Terminal):
@@ -1232,8 +1245,7 @@ def get_bare_quoted_string(value):
bare_quoted_string = BareQuotedString()
value = value[1:]
if value and value[0] == '"':
token, value = get_qcontent(value)
bare_quoted_string.append(token)
return bare_quoted_string, value[1:]
while value and value[0] != '"':
if value[0] in WSP:
token, value = get_fws(value)
@@ -2046,12 +2058,10 @@ def get_address_list(value):
address_list.defects.append(errors.InvalidHeaderDefect(
"invalid address in address-list"))
if value and value[0] != ',':
# Crap after address; treat it as an invalid mailbox.
# The mailbox info will still be available.
mailbox = address_list[-1][0]
mailbox.token_type = 'invalid-mailbox'
# Crap after address: add it to the address list
# as an invalid mailbox
token, value = get_invalid_mailbox(value, ',')
mailbox.extend(token)
address_list.append(Address([token]))
address_list.defects.append(errors.InvalidHeaderDefect(
"invalid address in address-list"))
if value: # Must be a , at this point.
@@ -2171,6 +2181,32 @@ def parse_message_id(value):
return message_id
def parse_message_ids(value):
"""in-reply-to = "In-Reply-To:" 1*msg-id CRLF
references = "References:" 1*msg-id CRLF
"""
message_id_list = MessageIDList()
while value:
if value[0] == ',':
# message id list separated with commas - this is invalid,
# but happens rather frequently in the wild
message_id_list.defects.append(
errors.InvalidHeaderDefect("comma in msg-id list"))
message_id_list.append(
WhiteSpaceTerminal(' ', 'invalid-comma-replacement'))
value = value[1:]
continue
try:
token, value = get_msg_id(value)
message_id_list.append(token)
except errors.HeaderParseError as ex:
token = get_unstructured(value)
message_id_list.append(InvalidMessageID(token))
message_id_list.defects.append(
errors.InvalidHeaderDefect("Invalid msg-id: {!r}".format(ex)))
break
return message_id_list
#
# XXX: As I begin to add additional header parsers, I'm realizing we probably
# have two level of parser routines: the get_XXX methods that get a token in
@@ -2788,8 +2824,12 @@ def _steal_trailing_WSP_if_exists(lines):
if lines and lines[-1] and lines[-1][-1] in WSP:
wsp = lines[-1][-1]
lines[-1] = lines[-1][:-1]
# gh-142006: if the line is now empty, remove it entirely.
if not lines[-1]:
lines.pop()
return wsp
def _refold_parse_tree(parse_tree, *, policy):
"""Return string of contents of parse_tree folded according to RFC rules.
@@ -2798,11 +2838,9 @@ def _refold_parse_tree(parse_tree, *, policy):
maxlen = policy.max_line_length or sys.maxsize
encoding = 'utf-8' if policy.utf8 else 'us-ascii'
lines = [''] # Folded lines to be output
leading_whitespace = '' # When we have whitespace between two encoded
# words, we may need to encode the whitespace
# at the beginning of the second word.
last_ew = None # Points to the last encoded character if there's an ew on
# the line
last_word_is_ew = False
last_ew = None # if there is an encoded word in the last line of lines,
# points to the encoded word's first character
last_charset = None
wrap_as_ew_blocked = 0
want_encoding = False # This is set to True if we need to encode this part
@@ -2837,6 +2875,7 @@ def _refold_parse_tree(parse_tree, *, policy):
if part.token_type == 'mime-parameters':
# Mime parameter folding (using RFC2231) is extra special.
_fold_mime_parameters(part, lines, maxlen, encoding)
last_word_is_ew = False
continue
if want_encoding and not wrap_as_ew_blocked:
@@ -2853,6 +2892,7 @@ def _refold_parse_tree(parse_tree, *, policy):
# XXX what if encoded_part has no leading FWS?
lines.append(newline)
lines[-1] += encoded_part
last_word_is_ew = False
continue
# Either this is not a major syntactic break, so we don't
# want it on a line by itself even if it fits, or it
@@ -2871,11 +2911,16 @@ def _refold_parse_tree(parse_tree, *, policy):
(last_charset == 'unknown-8bit' or
last_charset == 'utf-8' and charset != 'us-ascii')):
last_ew = None
last_ew = _fold_as_ew(tstr, lines, maxlen, last_ew,
part.ew_combine_allowed, charset, leading_whitespace)
# This whitespace has been added to the lines in _fold_as_ew()
# so clear it now.
leading_whitespace = ''
last_ew = _fold_as_ew(
tstr,
lines,
maxlen,
last_ew,
part.ew_combine_allowed,
charset,
last_word_is_ew,
)
last_word_is_ew = True
last_charset = charset
want_encoding = False
continue
@@ -2888,28 +2933,19 @@ def _refold_parse_tree(parse_tree, *, policy):
if len(tstr) <= maxlen - len(lines[-1]):
lines[-1] += tstr
last_word_is_ew = last_word_is_ew and not bool(tstr.strip(_WSP))
continue
# This part is too long to fit. The RFC wants us to break at
# "major syntactic breaks", so unless we don't consider this
# to be one, check if it will fit on the next line by itself.
leading_whitespace = ''
if (part.syntactic_break and
len(tstr) + 1 <= maxlen):
newline = _steal_trailing_WSP_if_exists(lines)
if newline or part.startswith_fws():
# We're going to fold the data onto a new line here. Due to
# the way encoded strings handle continuation lines, we need to
# be prepared to encode any whitespace if the next line turns
# out to start with an encoded word.
lines.append(newline + tstr)
whitespace_accumulator = []
for char in lines[-1]:
if char not in WSP:
break
whitespace_accumulator.append(char)
leading_whitespace = ''.join(whitespace_accumulator)
last_word_is_ew = (last_word_is_ew
and not bool(lines[-1].strip(_WSP)))
last_ew = None
continue
if not hasattr(part, 'encode'):
@@ -2924,6 +2960,13 @@ def _refold_parse_tree(parse_tree, *, policy):
[ValueTerminal(make_quoted_pairs(p), 'ptext')
for p in newparts] +
[ValueTerminal('"', 'ptext')])
if part.token_type == 'comment':
newparts = (
[ValueTerminal('(', 'ptext')] +
[ValueTerminal(make_parenthesis_pairs(p), 'ptext')
if p.token_type == 'ptext' else p
for p in newparts] +
[ValueTerminal(')', 'ptext')])
if not part.as_ew_allowed:
wrap_as_ew_blocked += 1
newparts.append(end_ew_not_allowed)
@@ -2942,10 +2985,11 @@ def _refold_parse_tree(parse_tree, *, policy):
else:
# We can't fold it onto the next line either...
lines[-1] += tstr
last_word_is_ew = last_word_is_ew and not bool(tstr.strip(_WSP))
return policy.linesep.join(lines) + policy.linesep
def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset, leading_whitespace):
def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset, last_word_is_ew):
"""Fold string to_encode into lines as encoded word, combining if allowed.
Return the new value for last_ew, or None if ew_combine_allowed is False.
@@ -2960,6 +3004,16 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset,
to_encode = str(
get_unstructured(lines[-1][last_ew:] + to_encode))
lines[-1] = lines[-1][:last_ew]
elif last_word_is_ew:
# If we are following up an encoded word with another encoded word,
# any white space between the two will be ignored when decoded.
# Therefore, we encode all to-be-displayed whitespace in the second
# encoded word.
len_without_wsp = len(lines[-1].rstrip(_WSP))
leading_whitespace = lines[-1][len_without_wsp:]
lines[-1] = (lines[-1][:len_without_wsp]
+ (' ' if leading_whitespace else ''))
to_encode = leading_whitespace + to_encode
elif to_encode[0] in WSP:
# We're joining this to non-encoded text, so don't encode
# the leading blank.
@@ -2988,20 +3042,13 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset,
while to_encode:
remaining_space = maxlen - len(lines[-1])
text_space = remaining_space - chrome_len - len(leading_whitespace)
text_space = remaining_space - chrome_len
if text_space <= 0:
lines.append(' ')
newline = _steal_trailing_WSP_if_exists(lines)
lines.append(newline or ' ')
new_last_ew = len(lines[-1])
continue
# If we are at the start of a continuation line, prepend whitespace
# (we only want to do this when the line starts with an encoded word
# but if we're folding in this helper function, then we know that we
# are going to be writing out an encoded word.)
if len(lines) > 1 and len(lines[-1]) == 1 and leading_whitespace:
encoded_word = _ew.encode(leading_whitespace, charset=encode_as)
lines[-1] += encoded_word
leading_whitespace = ''
to_encode_word = to_encode[:text_space]
encoded_word = _ew.encode(to_encode_word, charset=encode_as)
excess = len(encoded_word) - remaining_space
@@ -3013,7 +3060,6 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset,
excess = len(encoded_word) - remaining_space
lines[-1] += encoded_word
to_encode = to_encode[len(to_encode_word):]
leading_whitespace = ''
if to_encode:
lines.append(' ')

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2002-2007 Python Software Foundation
# Copyright (C) 2002 Python Software Foundation
# Contact: email-sig@python.org
"""Email address parsing code.
@@ -225,7 +225,7 @@ class AddrlistClass:
def __init__(self, field):
"""Initialize a new instance.
`field' is an unparsed address header field, containing
'field' is an unparsed address header field, containing
one or more addresses.
"""
self.specials = '()<>@,:;.\"[]'
@@ -426,14 +426,14 @@ class AddrlistClass:
def getdelimited(self, beginchar, endchars, allowcomments=True):
"""Parse a header fragment delimited by special characters.
`beginchar' is the start character for the fragment.
If self is not looking at an instance of `beginchar' then
'beginchar' is the start character for the fragment.
If self is not looking at an instance of 'beginchar' then
getdelimited returns the empty string.
`endchars' is a sequence of allowable end-delimiting characters.
'endchars' is a sequence of allowable end-delimiting characters.
Parsing stops when one of these is encountered.
If `allowcomments' is non-zero, embedded RFC 2822 comments are allowed
If 'allowcomments' is non-zero, embedded RFC 2822 comments are allowed
within the parsed fragment.
"""
if self.field[self.pos] != beginchar:
@@ -477,7 +477,7 @@ class AddrlistClass:
Optional atomends specifies a different set of end token delimiters
(the default is to use self.atomends). This is used e.g. in
getphraselist() since phrase endings must not include the `.' (which
getphraselist() since phrase endings must not include the '.' (which
is legal in phrases)."""
atomlist = ['']
if atomends is None:

View File

@@ -4,6 +4,7 @@ Allows fine grained feature control of how the package parses and emits data.
"""
import abc
import re
from email import header
from email import charset as _charset
from email.utils import _has_surrogates
@@ -14,6 +15,14 @@ __all__ = [
'compat32',
]
# validation regex from RFC 5322, equivalent to pattern re.compile("[!-9;-~]+$")
valid_header_name_re = re.compile("[\041-\071\073-\176]+$")
def validate_header_name(name):
# Validate header name according to RFC 5322
if not valid_header_name_re.match(name):
raise ValueError(
f"Header field name contains invalid characters: {name!r}")
class _PolicyBase:
@@ -150,7 +159,7 @@ class Policy(_PolicyBase, metaclass=abc.ABCMeta):
wrapping is done. Default is 78.
mangle_from_ -- a flag that, when True escapes From_ lines in the
body of the message by putting a `>' in front of
body of the message by putting a '>' in front of
them. This is used when the message is being
serialized by a generator. Default: False.
@@ -314,6 +323,7 @@ class Compat32(Policy):
"""+
The name and value are returned unmodified.
"""
validate_header_name(name)
return (name, value)
def header_fetch_parse(self, name, value):

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2002-2007 Python Software Foundation
# Copyright (C) 2002 Python Software Foundation
# Author: Ben Gertzfield
# Contact: email-sig@python.org
@@ -15,7 +15,7 @@ This module provides an interface to encode and decode both headers and bodies
with Base64 encoding.
RFC 2045 defines a method for including character set information in an
`encoded-word' in a header. This method is commonly used for 8-bit real names
'encoded-word' in a header. This method is commonly used for 8-bit real names
in To:, From:, Cc:, etc. fields, as well as Subject: lines.
This module does not do the line wrapping or end-of-line character conversion

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2007 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Ben Gertzfield, Barry Warsaw
# Contact: email-sig@python.org
@@ -175,7 +175,7 @@ class Charset:
module expose the following information about a character set:
input_charset: The initial character set specified. Common aliases
are converted to their `official' email names (e.g. latin_1
are converted to their 'official' email names (e.g. latin_1
is converted to iso-8859-1). Defaults to 7-bit us-ascii.
header_encoding: If the character set must be encoded before it can be
@@ -245,7 +245,7 @@ class Charset:
def get_body_encoding(self):
"""Return the content-transfer-encoding used for body encoding.
This is either the string `quoted-printable' or `base64' depending on
This is either the string 'quoted-printable' or 'base64' depending on
the encoding used, or it is a function in which case you should call
the function with a single argument, the Message object being
encoded. The function should then set the Content-Transfer-Encoding

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2006 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org

2
Lib/email/errors.py vendored
View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2006 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2004-2006 Python Software Foundation
# Copyright (C) 2004 Python Software Foundation
# Authors: Baxter, Wouters and Warsaw
# Contact: email-sig@python.org
@@ -30,7 +30,7 @@ from io import StringIO
NLCRE = re.compile(r'\r\n|\r|\n')
NLCRE_bol = re.compile(r'(\r\n|\r|\n)')
NLCRE_eol = re.compile(r'(\r\n|\r|\n)\Z')
NLCRE_eol = re.compile(r'(\r\n|\r|\n)\z')
NLCRE_crack = re.compile(r'(\r\n|\r|\n)')
# RFC 5322 section 3.6.8 Optional fields. ftext is %d33-57 / %d59-126, Any character
# except controls, SP, and ":".
@@ -504,10 +504,9 @@ class FeedParser:
self._input.unreadline(line)
return
else:
# Weirdly placed unix-from line. Note this as a defect
# and ignore it.
# Weirdly placed unix-from line.
defect = errors.MisplacedEnvelopeHeaderDefect(line)
self._cur.defects.append(defect)
self.policy.handle_defect(self._cur, defect)
continue
# Split the line on the colon separating field name from value.
# There will always be a colon, because if there wasn't the part of
@@ -519,7 +518,7 @@ class FeedParser:
# message. Track the error but keep going.
if i == 0:
defect = errors.InvalidHeaderDefect("Missing header name.")
self._cur.defects.append(defect)
self.policy.handle_defect(self._cur, defect)
continue
assert i>0, "_parse_headers fed line with no : and no leading WS"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2010 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org
@@ -22,6 +22,7 @@ NL = '\n' # XXX: no longer used by the code below.
NLCRE = re.compile(r'\r\n|\r|\n')
fcre = re.compile(r'^From ', re.MULTILINE)
NEWLINE_WITHOUT_FWSP = re.compile(r'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
NEWLINE_WITHOUT_FWSP_BYTES = re.compile(br'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
class Generator:
@@ -43,7 +44,7 @@ class Generator:
Optional mangle_from_ is a flag that, when True (the default if policy
is not set), escapes From_ lines in the body of the message by putting
a `>' in front of them.
a '>' in front of them.
Optional maxheaderlen specifies the longest length for a non-continued
header. When a header line is longer (in characters, with tabs
@@ -76,7 +77,7 @@ class Generator:
unixfrom is a flag that forces the printing of a Unix From_ delimiter
before the first object in the message tree. If the original message
has no From_ delimiter, a `standard' one is crafted. By default, this
has no From_ delimiter, a 'standard' one is crafted. By default, this
is False to inhibit the printing of any From_ delimiter.
Note that for subobjects, no From_ line is printed.
@@ -227,7 +228,7 @@ class Generator:
folded = self.policy.fold(h, v)
if self.policy.verify_generated_headers:
linesep = self.policy.linesep
if not folded.endswith(self.policy.linesep):
if not folded.endswith(linesep):
raise HeaderWriteError(
f'folded header does not end with {linesep!r}: {folded!r}')
if NEWLINE_WITHOUT_FWSP.search(folded.removesuffix(linesep)):
@@ -391,7 +392,7 @@ class Generator:
b = boundary
counter = 0
while True:
cre = cls._compile_re('^--' + re.escape(b) + '(--)?$', re.MULTILINE)
cre = cls._compile_re('^--' + re.escape(b) + '(--)?\r?$', re.MULTILINE)
if not cre.search(text):
break
b = boundary + '.' + str(counter)
@@ -429,7 +430,16 @@ class BytesGenerator(Generator):
# This is almost the same as the string version, except for handling
# strings with 8bit bytes.
for h, v in msg.raw_items():
self._fp.write(self.policy.fold_binary(h, v))
folded = self.policy.fold_binary(h, v)
if self.policy.verify_generated_headers:
linesep = self.policy.linesep.encode()
if not folded.endswith(linesep):
raise HeaderWriteError(
f'folded header does not end with {linesep!r}: {folded!r}')
if NEWLINE_WITHOUT_FWSP_BYTES.search(folded.removesuffix(linesep)):
raise HeaderWriteError(
f'folded header contains newline: {folded!r}')
self._fp.write(folded)
# A blank line always separates headers from body
self.write(self._NL)
@@ -467,7 +477,7 @@ class DecodedGenerator(Generator):
argument is allowed.
Walks through all subparts of a message. If the subpart is of main
type `text', then it prints the decoded payload of the subpart.
type 'text', then it prints the decoded payload of the subpart.
Otherwise, fmt is a format string that is used instead of the message
payload. fmt is expanded with the following keywords (in

8
Lib/email/header.py vendored
View File

@@ -1,4 +1,4 @@
# Copyright (C) 2002-2007 Python Software Foundation
# Copyright (C) 2002 Python Software Foundation
# Author: Ben Gertzfield, Barry Warsaw
# Contact: email-sig@python.org
@@ -201,7 +201,7 @@ class Header:
The maximum line length can be specified explicitly via maxlinelen. For
splitting the first line to a shorter value (to account for the field
header which isn't included in s, e.g. `Subject') pass in the name of
header which isn't included in s, e.g. 'Subject') pass in the name of
the field in header_name. The default maxlinelen is 78 as recommended
by RFC 2822.
@@ -285,7 +285,7 @@ class Header:
output codec of the charset. If the string cannot be encoded to the
output codec, a UnicodeError will be raised.
Optional `errors' is passed as the errors argument to the decode
Optional 'errors' is passed as the errors argument to the decode
call if s is a byte string.
"""
if charset is None:
@@ -335,7 +335,7 @@ class Header:
Optional splitchars is a string containing characters which should be
given extra weight by the splitting algorithm during normal header
wrapping. This is in very rough support of RFC 2822's `higher level
wrapping. This is in very rough support of RFC 2822's 'higher level
syntactic breaks': split points preceded by a splitchar are preferred
during line splitting, with the characters preferred in the order in
which they appear in the string. Space and tab may be included in the

View File

@@ -534,6 +534,18 @@ class MessageIDHeader:
kwds['defects'].extend(parse_tree.all_defects)
class ReferencesHeader:
max_count = 1
value_parser = staticmethod(parser.parse_message_ids)
@classmethod
def parse(cls, value, kwds):
kwds['parse_tree'] = parse_tree = cls.value_parser(value)
kwds['decoded'] = str(parse_tree)
kwds['defects'].extend(parse_tree.all_defects)
# The header factory #
_default_header_map = {
@@ -557,6 +569,8 @@ _default_header_map = {
'content-disposition': ContentDispositionHeader,
'content-transfer-encoding': ContentTransferEncodingHeader,
'message-id': MessageIDHeader,
'in-reply-to': ReferencesHeader,
'references': ReferencesHeader,
}
class HeaderRegistry:

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2006 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org
@@ -43,8 +43,8 @@ def body_line_iterator(msg, decode=False):
def typed_subpart_iterator(msg, maintype='text', subtype=None):
"""Iterate over the subparts with a given MIME type.
Use `maintype' as the main MIME type to match against; this defaults to
"text". Optional `subtype' is the MIME subtype to match against; if
Use 'maintype' as the main MIME type to match against; this defaults to
"text". Optional 'subtype' is the MIME subtype to match against; if
omitted, only the main type is matched.
"""
for subpart in msg.walk():

28
Lib/email/message.py vendored
View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2007 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org
@@ -21,7 +21,7 @@ Charset = _charset.Charset
SEMISPACE = '; '
# Regular expression that matches `special' characters in parameters, the
# Regular expression that matches 'special' characters in parameters, the
# existence of which force quoting of the parameter value.
tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
@@ -147,7 +147,7 @@ class Message:
multipart or a message/rfc822), then the payload is a list of Message
objects, otherwise it is a string.
Message objects implement part of the `mapping' interface, which assumes
Message objects implement part of the 'mapping' interface, which assumes
there is exactly one occurrence of the header per message. Some headers
do in fact appear multiple times (e.g. Received) and for those headers,
you must use the explicit API to set or get all the headers. Not all of
@@ -609,7 +609,7 @@ class Message:
"""Return the message's content type.
The returned string is coerced to lower case of the form
`maintype/subtype'. If there was no Content-Type header in the
'maintype/subtype'. If there was no Content-Type header in the
message, the default type as given by get_default_type() will be
returned. Since according to RFC 2045, messages always have a default
type this will always return a value.
@@ -632,7 +632,7 @@ class Message:
def get_content_maintype(self):
"""Return the message's main content type.
This is the `maintype' part of the string returned by
This is the 'maintype' part of the string returned by
get_content_type().
"""
ctype = self.get_content_type()
@@ -641,14 +641,14 @@ class Message:
def get_content_subtype(self):
"""Returns the message's sub-content type.
This is the `subtype' part of the string returned by
This is the 'subtype' part of the string returned by
get_content_type().
"""
ctype = self.get_content_type()
return ctype.split('/')[1]
def get_default_type(self):
"""Return the `default' content type.
"""Return the 'default' content type.
Most messages have a default content type of text/plain, except for
messages that are subparts of multipart/digest containers. Such
@@ -657,7 +657,7 @@ class Message:
return self._default_type
def set_default_type(self, ctype):
"""Set the `default' content type.
"""Set the 'default' content type.
ctype should be either "text/plain" or "message/rfc822", although this
is not enforced. The default content type is not stored in the
@@ -690,8 +690,8 @@ class Message:
"""Return the message's Content-Type parameters, as a list.
The elements of the returned list are 2-tuples of key/value pairs, as
split on the `=' sign. The left hand side of the `=' is the key,
while the right hand side is the value. If there is no `=' sign in
split on the '=' sign. The left hand side of the '=' is the key,
while the right hand side is the value. If there is no '=' sign in
the parameter the value is the empty string. The value is as
described in the get_param() method.
@@ -851,9 +851,9 @@ class Message:
"""Return the filename associated with the payload if present.
The filename is extracted from the Content-Disposition header's
`filename' parameter, and it is unquoted. If that header is missing
the `filename' parameter, this method falls back to looking for the
`name' parameter.
'filename' parameter, and it is unquoted. If that header is missing
the 'filename' parameter, this method falls back to looking for the
'name' parameter.
"""
missing = object()
filename = self.get_param('filename', missing, 'content-disposition')
@@ -866,7 +866,7 @@ class Message:
def get_boundary(self, failobj=None):
"""Return the boundary associated with the payload if present.
The boundary is extracted from the Content-Type header's `boundary'
The boundary is extracted from the Content-Type header's 'boundary'
parameter, and it is unquoted.
"""
missing = object()

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2006 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Keith Dart
# Contact: email-sig@python.org

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2007 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Anthony Baxter
# Contact: email-sig@python.org

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2006 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2006 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2006 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2002-2006 Python Software Foundation
# Copyright (C) 2002 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org
@@ -21,7 +21,7 @@ class MIMEMultipart(MIMEBase):
Content-Type and MIME-Version headers.
_subtype is the subtype of the multipart content type, defaulting to
`mixed'.
'mixed'.
boundary is the multipart boundary string. By default it is
calculated as needed.

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2002-2006 Python Software Foundation
# Copyright (C) 2002 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2006 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org

2
Lib/email/parser.py vendored
View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2007 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Barry Warsaw, Thomas Wouters, Anthony Baxter
# Contact: email-sig@python.org

9
Lib/email/policy.py vendored
View File

@@ -4,7 +4,13 @@ code that adds all the email6 features.
import re
import sys
from email._policybase import Policy, Compat32, compat32, _extend_docstrings
from email._policybase import (
Compat32,
Policy,
_extend_docstrings,
compat32,
validate_header_name
)
from email.utils import _has_surrogates
from email.headerregistry import HeaderRegistry as HeaderRegistry
from email.contentmanager import raw_data_manager
@@ -138,6 +144,7 @@ class EmailPolicy(Policy):
CR or LF characters.
"""
validate_header_name(name)
if hasattr(value, 'name') and value.name.lower() == name.lower():
return (name, value)
if isinstance(value, str) and len(value.splitlines())>1:

View File

@@ -1,11 +1,11 @@
# Copyright (C) 2001-2006 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Ben Gertzfield
# Contact: email-sig@python.org
"""Quoted-printable content transfer encoding per RFCs 2045-2047.
This module handles the content transfer encoding method defined in RFC 2045
to encode US ASCII-like 8-bit data called `quoted-printable'. It is used to
to encode US ASCII-like 8-bit data called 'quoted-printable'. It is used to
safely encode text that is in a character set similar to the 7-bit US ASCII
character set, but that includes some 8-bit characters that are normally not
allowed in email bodies or headers.
@@ -17,7 +17,7 @@ This module provides an interface to encode and decode both headers and bodies
with quoted-printable encoding.
RFC 2045 defines a method for including character set information in an
`encoded-word' in a header. This method is commonly used for 8-bit real names
'encoded-word' in a header. This method is commonly used for 8-bit real names
in To:/From:/Cc: etc. fields, as well as Subject: lines.
This module does not do the line wrapping or end-of-line character
@@ -127,7 +127,7 @@ def quote(c):
def header_encode(header_bytes, charset='iso-8859-1'):
"""Encode a single header line with quoted-printable (like) encoding.
Defined in RFC 2045, this `Q' encoding is similar to quoted-printable, but
Defined in RFC 2045, this 'Q' encoding is similar to quoted-printable, but
used specifically for email header fields to allow charsets with mostly 7
bit characters (and some 8 bit) to remain more or less readable in non-RFC
2045 aware mail clients.
@@ -272,7 +272,7 @@ def decode(encoded, eol=NL):
decoded += eol
# Special case if original string did not end with eol
if encoded[-1] not in '\r\n' and decoded.endswith(eol):
decoded = decoded[:-1]
decoded = decoded[:-len(eol)]
return decoded
@@ -290,7 +290,7 @@ def _unquote_match(match):
# Header decoding is done a bit differently
def header_decode(s):
"""Decode a string encoded with RFC 2045 MIME header `Q' encoding.
"""Decode a string encoded with RFC 2045 MIME header 'Q' encoding.
This function does not parse a full MIME header value encoded with
quoted-printable (like =?iso-8859-1?q?Hello_World?=) -- please use

12
Lib/email/utils.py vendored
View File

@@ -1,4 +1,4 @@
# Copyright (C) 2001-2010 Python Software Foundation
# Copyright (C) 2001 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org
@@ -472,23 +472,15 @@ def collapse_rfc2231_value(value, errors='replace',
# better than not having it.
#
def localtime(dt=None, isdst=None):
def localtime(dt=None):
"""Return local time as an aware datetime object.
If called without arguments, return current time. Otherwise *dt*
argument should be a datetime instance, and it is converted to the
local time zone according to the system time zone database. If *dt* is
naive (that is, dt.tzinfo is None), it is assumed to be in local time.
The isdst parameter is ignored.
"""
if isdst is not None:
import warnings
warnings._deprecated(
"The 'isdst' parameter to 'localtime'",
message='{name} is deprecated and slated for removal in Python {remove}',
remove=(3, 14),
)
if dt is None:
dt = datetime.datetime.now()
return dt.astimezone()

View File

@@ -33,6 +33,7 @@ import sys
from . import aliases
_cache = {}
_MAXCACHE = 500
_unknown = '--unknown--'
_import_tail = ['*']
_aliases = aliases.aliases
@@ -115,6 +116,8 @@ def search_function(encoding):
if mod is None:
# Cache misses
if len(_cache) >= _MAXCACHE:
_cache.clear()
_cache[encoding] = None
return None
@@ -136,6 +139,8 @@ def search_function(encoding):
entry = codecs.CodecInfo(*entry)
# Cache the codec registry entry
if len(_cache) >= _MAXCACHE:
_cache.clear()
_cache[encoding] = entry
# Register its aliases (without overwriting previously registered

View File

@@ -10,13 +10,14 @@ from shutil import copy2
__all__ = ["version", "bootstrap"]
_PIP_VERSION = "25.3"
_PIP_VERSION = "26.1.1"
# Directory of system wheel packages. Some Linux distribution packaging
# policies recommend against bundling dependencies. For example, Fedora
# installs wheel packages in the /usr/share/python-wheels/ directory and don't
# install the ensurepip._bundled package.
if (_pkg_dir := sysconfig.get_config_var('WHEEL_PKG_DIR')) is not None:
_pkg_dir = sysconfig.get_config_var('WHEEL_PKG_DIR')
if _pkg_dir:
_WHEEL_PKG_DIR = Path(_pkg_dir).resolve()
else:
_WHEEL_PKG_DIR = None

27
Lib/glob.py vendored
View File

@@ -15,7 +15,7 @@ __all__ = ["glob", "iglob", "escape", "translate"]
def glob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
include_hidden=False):
"""Return a list of paths matching a pathname pattern.
"""Return a list of paths matching a `pathname` pattern.
The pattern may contain simple shell-style wildcards a la
fnmatch. Unlike fnmatch, filenames starting with a
@@ -25,6 +25,15 @@ def glob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
The order of the returned list is undefined. Sort it if you need a
particular order.
If `root_dir` is not None, it should be a path-like object specifying the
root directory for searching. It has the same effect as changing the
current directory before calling it (without actually
changing it). If pathname is relative, the result will contain
paths relative to `root_dir`.
If `dir_fd` is not None, it should be a file descriptor referring to a
directory, and paths will then be relative to that directory.
If `include_hidden` is true, the patterns '*', '?', '**' will match hidden
directories.
@@ -36,7 +45,7 @@ def glob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
def iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
include_hidden=False):
"""Return an iterator which yields the paths matching a pathname pattern.
"""Return an iterator which yields the paths matching a `pathname` pattern.
The pattern may contain simple shell-style wildcards a la
fnmatch. However, unlike fnmatch, filenames starting with a
@@ -46,7 +55,19 @@ def iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
The order of the returned paths is undefined. Sort them if you need a
particular order.
If recursive is true, the pattern '**' will match any files and
If `root_dir` is not None, it should be a path-like object specifying
the root directory for searching. It has the same effect as changing
the current directory before calling it (without actually
changing it). If pathname is relative, the result will contain
paths relative to `root_dir`.
If `dir_fd` is not None, it should be a file descriptor referring to a
directory, and paths will then be relative to that directory.
If `include_hidden` is true, the patterns '*', '?', '**' will match hidden
directories.
If `recursive` is true, the pattern '**' will match any files and
zero or more directories and subdirectories.
"""
sys.audit("glob.glob", pathname, recursive)

11
Lib/http/client.py vendored
View File

@@ -972,13 +972,22 @@ class HTTPConnection:
return ip
def _tunnel(self):
if _contains_disallowed_url_pchar_re.search(self._tunnel_host):
raise ValueError('Tunnel host can\'t contain control characters %r'
% (self._tunnel_host,))
connect = b"CONNECT %s:%d %s\r\n" % (
self._wrap_ipv6(self._tunnel_host.encode("idna")),
self._tunnel_port,
self._http_vsn_str.encode("ascii"))
headers = [connect]
for header, value in self._tunnel_headers.items():
headers.append(f"{header}: {value}\r\n".encode("latin-1"))
header_bytes = header.encode("latin-1")
value_bytes = value.encode("latin-1")
if not _is_legal_header_name(header_bytes):
raise ValueError('Invalid header name %r' % (header_bytes,))
if _is_illegal_header_value(value_bytes):
raise ValueError('Invalid header value %r' % (value_bytes,))
headers.append(b"%s: %s\r\n" % (header_bytes, value_bytes))
headers.append(b"\r\n")
# Making a single send() call instead of one per line encourages
# the host OS to use a more optimal packet size instead of

30
Lib/http/cookies.py vendored
View File

@@ -337,9 +337,16 @@ class Morsel(dict):
key = key.lower()
if key not in self._reserved:
raise CookieError("Invalid attribute %r" % (key,))
if _has_control_character(key, val):
raise CookieError("Control characters are not allowed in "
f"cookies {key!r} {val!r}")
data[key] = val
dict.update(self, data)
def __ior__(self, values):
self.update(values)
return self
def isReservedKey(self, K):
return K.lower() in self._reserved
@@ -365,9 +372,15 @@ class Morsel(dict):
}
def __setstate__(self, state):
self._key = state['key']
self._value = state['value']
self._coded_value = state['coded_value']
key = state['key']
value = state['value']
coded_value = state['coded_value']
if _has_control_character(key, value, coded_value):
raise CookieError("Control characters are not allowed in cookies "
f"{key!r} {value!r} {coded_value!r}")
self._key = key
self._value = value
self._coded_value = coded_value
def output(self, attrs=None, header="Set-Cookie:"):
return "%s %s" % (header, self.OutputString(attrs))
@@ -378,14 +391,21 @@ class Morsel(dict):
return '<%s: %s>' % (self.__class__.__name__, self.OutputString())
def js_output(self, attrs=None):
import base64
# Print javascript
output_string = self.OutputString(attrs)
if _has_control_character(output_string):
raise CookieError("Control characters are not allowed in cookies")
# Base64-encode value to avoid template
# injection in cookie values.
output_encoded = base64.b64encode(output_string.encode('utf-8')).decode("ascii")
return """
<script type="text/javascript">
<!-- begin hiding
document.cookie = \"%s\";
document.cookie = atob(\"%s\");
// end hiding -->
</script>
""" % (self.OutputString(attrs).replace('"', r'\"'))
""" % (output_encoded,)
def OutputString(self, attrs=None):
# Build up our result

5
Lib/inspect.py vendored
View File

@@ -1,7 +1,7 @@
"""Get useful information from live Python objects.
This module encapsulates the interface provided by the internal special
attributes (co_*, im_*, tb_*, etc.) in a friendlier fashion.
attributes (co_*, tb_*, etc.) in a friendlier fashion.
It also provides some help for examining source code and class layout.
Here are some of the useful functions provided by this module:
@@ -2660,11 +2660,12 @@ class Parameter:
The annotation for the parameter if specified. If the
parameter has no annotation, this attribute is set to
`Parameter.empty`.
* kind : str
* kind
Describes how argument values are bound to the parameter.
Possible values: `Parameter.POSITIONAL_ONLY`,
`Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`,
`Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
Every value has a `description` attribute describing meaning.
"""
__slots__ = ('_name', '_kind', '_default', '_annotation')

View File

@@ -1475,8 +1475,6 @@ class Logger(Filterer):
level, and "input.csv", "input.xls" and "input.gnu" for the sub-levels.
There is no arbitrary limit to the depth of nesting.
"""
_tls = threading.local()
def __init__(self, name, level=NOTSET):
"""
Initialize the logger with a name and an optional level.
@@ -1673,19 +1671,14 @@ class Logger(Filterer):
This method is used for unpickled records received from a socket, as
well as those created locally. Logger-level filtering is applied.
"""
if self._is_disabled():
if self.disabled:
return
self._tls.in_progress = True
try:
maybe_record = self.filter(record)
if not maybe_record:
return
if isinstance(maybe_record, LogRecord):
record = maybe_record
self.callHandlers(record)
finally:
self._tls.in_progress = False
maybe_record = self.filter(record)
if not maybe_record:
return
if isinstance(maybe_record, LogRecord):
record = maybe_record
self.callHandlers(record)
def addHandler(self, hdlr):
"""
@@ -1773,7 +1766,7 @@ class Logger(Filterer):
"""
Is this logger enabled for level 'level'?
"""
if self._is_disabled():
if self.disabled:
return False
try:
@@ -1823,11 +1816,6 @@ class Logger(Filterer):
if isinstance(item, Logger) and item.parent is self and
_hierlevel(item) == 1 + _hierlevel(item.parent))
def _is_disabled(self):
# We need to use getattr as it will only be set the first time a log
# message is recorded on any given thread
return self.disabled or getattr(self._tls, 'in_progress', False)
def __repr__(self):
level = getLevelName(self.getEffectiveLevel())
return '<%s %s (%s)>' % (self.__class__.__name__, self.name, level)
@@ -1864,9 +1852,9 @@ class LoggerAdapter(object):
def __init__(self, logger, extra=None, merge_extra=False):
"""
Initialize the adapter with a logger and a dict-like object which
provides contextual information. This constructor signature allows
easy stacking of LoggerAdapters, if so desired.
Initialize the adapter with a logger and an optional dict-like object
which provides contextual information. This constructor signature
allows easy stacking of LoggerAdapters, if so desired.
You can effectively pass keyword arguments as shown in the
following example:
@@ -1897,8 +1885,9 @@ class LoggerAdapter(object):
Normally, you'll only need to override this one method in a
LoggerAdapter subclass for your specific needs.
"""
if self.merge_extra and "extra" in kwargs:
kwargs["extra"] = {**self.extra, **kwargs["extra"]}
if self.merge_extra and kwargs.get("extra") is not None:
if self.extra is not None:
kwargs["extra"] = {**self.extra, **kwargs["extra"]}
else:
kwargs["extra"] = self.extra
return msg, kwargs

14
Lib/logging/config.py vendored
View File

@@ -865,6 +865,8 @@ class DictConfigurator(BaseConfigurator):
else:
factory = klass
kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
# When deprecation ends for using the 'strm' parameter, remove the
# "except TypeError ..."
try:
result = factory(**kwargs)
except TypeError as te:
@@ -876,6 +878,15 @@ class DictConfigurator(BaseConfigurator):
#(e.g. by Django)
kwargs['strm'] = kwargs.pop('stream')
result = factory(**kwargs)
import warnings
warnings.warn(
"Support for custom logging handlers with the 'strm' argument "
"is deprecated and scheduled for removal in Python 3.16. "
"Define handlers with the 'stream' argument instead.",
DeprecationWarning,
stacklevel=2,
)
if formatter:
result.setFormatter(formatter)
if level is not None:
@@ -1006,7 +1017,8 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None):
A simple TCP socket-based logging config receiver.
"""
allow_reuse_address = 1
allow_reuse_address = True
allow_reuse_port = False
def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
handler=None, ready=None, verify=None):

View File

@@ -196,7 +196,11 @@ class RotatingFileHandler(BaseRotatingHandler):
if self.stream is None: # delay was set...
self.stream = self._open()
if self.maxBytes > 0: # are we rolling over?
pos = self.stream.tell()
try:
pos = self.stream.tell()
except io.UnsupportedOperation:
# gh-143237: Never rollover a named pipe.
return False
if not pos:
# gh-116263: Never rollover an empty file
return False
@@ -855,7 +859,7 @@ class SysLogHandler(logging.Handler):
}
def __init__(self, address=('localhost', SYSLOG_UDP_PORT),
facility=LOG_USER, socktype=None):
facility=LOG_USER, socktype=None, timeout=None):
"""
Initialize a handler.
@@ -872,6 +876,7 @@ class SysLogHandler(logging.Handler):
self.address = address
self.facility = facility
self.socktype = socktype
self.timeout = timeout
self.socket = None
self.createSocket()
@@ -933,6 +938,8 @@ class SysLogHandler(logging.Handler):
err = sock = None
try:
sock = socket.socket(af, socktype, proto)
if self.timeout:
sock.settimeout(self.timeout)
if socktype == socket.SOCK_STREAM:
sock.connect(sa)
break
@@ -1529,6 +1536,19 @@ class QueueListener(object):
self._thread = None
self.respect_handler_level = respect_handler_level
def __enter__(self):
"""
For use as a context manager. Starts the listener.
"""
self.start()
return self
def __exit__(self, *args):
"""
For use as a context manager. Stops the listener.
"""
self.stop()
def dequeue(self, block):
"""
Dequeue a record and return it, optionally blocking.

12
Lib/pickle.py vendored
View File

@@ -904,17 +904,11 @@ class _Pickler:
# Write data in-band
# XXX The C implementation avoids a copy here
buf = m.tobytes()
in_memo = id(buf) in self.memo
if m.readonly:
if in_memo:
self._save_bytes_no_memo(buf)
else:
self.save_bytes(buf)
self._save_bytes_no_memo(buf)
else:
if in_memo:
self._save_bytearray_no_memo(buf)
else:
self.save_bytearray(buf)
self._save_bytearray_no_memo(buf)
self.memoize(obj)
else:
# Write data out-of-band
self.write(NEXT_BUFFER)

117
Lib/platform.py vendored Executable file → Normal file
View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
""" This module tries to retrieve as much platform-identifying data as
possible. It makes this information available via function APIs.
@@ -33,6 +31,7 @@
#
# <see CVS and SVN checkin messages for history>
#
# 1.0.9 - added invalidate_caches() function to invalidate cached values
# 1.0.8 - changed Windows support to read version from kernel32.dll
# 1.0.7 - added DEV_NULL
# 1.0.6 - added linux_distribution()
@@ -111,7 +110,7 @@ __copyright__ = """
"""
__version__ = '1.0.8'
__version__ = '1.0.9'
import collections
import os
@@ -174,6 +173,11 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384):
"""
if not executable:
if sys.platform == "emscripten":
# Emscripten's os.confstr reports that it is glibc, so special case
# it.
ver = ".".join(str(x) for x in sys._emscripten_info.emscripten_version)
return ("emscripten", ver)
try:
ver = os.confstr('CS_GNU_LIBC_VERSION')
# parse 'glibc 2.28' as ('glibc', '2.28')
@@ -190,22 +194,26 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384):
# sys.executable is not set.
return lib, version
libc_search = re.compile(b'(__libc_init)'
b'|'
b'(GLIBC_([0-9.]+))'
b'|'
br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)', re.ASCII)
libc_search = re.compile(br"""
(__libc_init)
| (GLIBC_([0-9.]+))
| (libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)
| (musl-([0-9.]+))
| ((?:libc\.|ld-)musl(?:-\w+)?.so(?:\.(\d[0-9.]*))?)
""",
re.ASCII | re.VERBOSE)
V = _comparable_version
# We use os.path.realpath()
# here to work around problems with Cygwin not being
# able to open symlinks for reading
executable = os.path.realpath(executable)
ver = None
with open(executable, 'rb') as f:
binary = f.read(chunksize)
pos = 0
while pos < len(binary):
if b'libc' in binary or b'GLIBC' in binary:
if b'libc' in binary or b'GLIBC' in binary or b'musl' in binary:
m = libc_search.search(binary, pos)
else:
m = None
@@ -217,26 +225,35 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384):
continue
if not m:
break
libcinit, glibc, glibcversion, so, threads, soversion = [
s.decode('latin1') if s is not None else s
for s in m.groups()]
decoded_groups = [s.decode('latin1') if s is not None else s
for s in m.groups()]
(libcinit, glibc, glibcversion, so, threads, soversion,
musl, muslversion, musl_so, musl_sover) = decoded_groups
if libcinit and not lib:
lib = 'libc'
elif glibc:
if lib != 'glibc':
lib = 'glibc'
version = glibcversion
elif V(glibcversion) > V(version):
version = glibcversion
ver = glibcversion
elif V(glibcversion) > V(ver):
ver = glibcversion
elif so:
if lib != 'glibc':
if lib not in ('glibc', 'musl'):
lib = 'libc'
if soversion and (not version or V(soversion) > V(version)):
version = soversion
if threads and version[-len(threads):] != threads:
version = version + threads
if soversion and (not ver or V(soversion) > V(ver)):
ver = soversion
if threads and ver[-len(threads):] != threads:
ver = ver + threads
elif musl:
lib = 'musl'
if not ver or V(muslversion) > V(ver):
ver = muslversion
elif musl_so:
lib = 'musl'
if musl_sover and (not ver or V(musl_sover) > V(ver)):
ver = musl_sover
pos = m.end()
return lib, version
return lib, version if ver is None else ver
def _norm_version(version, build=''):
@@ -549,7 +566,7 @@ def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')):
warnings._deprecated('java_ver', remove=(3, 15))
# Import the needed APIs
try:
import java.lang
import java.lang # noqa: F401
except ImportError:
return release, vendor, vminfo, osinfo
@@ -1192,7 +1209,7 @@ def _sys_version(sys_version=None):
# CPython
cpython_sys_version_parser = re.compile(
r'([\w.+]+)\s*' # "version<space>"
r'(?:experimental free-threading build\s+)?' # "free-threading-build<space>"
r'(?:free-threading build\s+)?' # "free-threading-build<space>"
r'\(#?([^,]+)' # "(#buildno"
r'(?:,\s*([\w ]*)' # ", builddate"
r'(?:,\s*([\w :]*))?)?\)\s*' # ", buildtime)<space>"
@@ -1449,11 +1466,55 @@ def freedesktop_os_release():
return _os_release_cache.copy()
def invalidate_caches():
"""Invalidate the cached results."""
global _uname_cache
_uname_cache = None
global _os_release_cache
_os_release_cache = None
_sys_version_cache.clear()
_platform_cache.clear()
### Command line interface
if __name__ == '__main__':
# Default is to print the aliased verbose platform string
terse = ('terse' in sys.argv or '--terse' in sys.argv)
aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv)
def _parse_args(args: list[str] | None):
import argparse
parser = argparse.ArgumentParser(color=True)
parser.add_argument("args", nargs="*", choices=["nonaliased", "terse"])
parser.add_argument(
"--terse",
action="store_true",
help=(
"return only the absolute minimum information needed "
"to identify the platform"
),
)
parser.add_argument(
"--nonaliased",
dest="aliased",
action="store_false",
help=(
"disable system/OS name aliasing. If aliasing is enabled, "
"some platforms report system names different from "
"their common names, e.g. SunOS is reported as Solaris"
),
)
return parser.parse_args(args)
def _main(args: list[str] | None = None):
args = _parse_args(args)
terse = args.terse or ("terse" in args.args)
aliased = args.aliased and ('nonaliased' not in args.args)
print(platform(aliased, terse))
sys.exit(0)
if __name__ == "__main__":
_main()

6
Lib/plistlib.py vendored
View File

@@ -21,7 +21,7 @@ datetime.datetime objects.
Generate Plist example:
import datetime
import datetime as dt
import plistlib
pl = dict(
@@ -37,7 +37,7 @@ Generate Plist example:
),
someData = b"<binary gunk>",
someMoreData = b"<lots of binary gunk>" * 10,
aDate = datetime.datetime.now()
aDate = dt.datetime.now()
)
print(plistlib.dumps(pl).decode())
@@ -384,7 +384,7 @@ class _PlistWriter(_DumbXMLWriter):
self._indent_level -= 1
maxlinelength = max(
16,
76 - len(self.indent.replace(b"\t", b" " * 8) * self._indent_level))
76 - len((self.indent * self._indent_level).expandtabs()))
for line in _encode_base64(data, maxlinelength).split(b"\n"):
if line:

47
Lib/pty.py vendored
View File

@@ -32,27 +32,18 @@ def openpty():
except (AttributeError, OSError):
pass
master_fd, slave_name = _open_terminal()
slave_fd = slave_open(slave_name)
return master_fd, slave_fd
def master_open():
"""master_open() -> (master_fd, slave_name)
Open a pty master and return the fd, and the filename of the slave end.
Deprecated, use openpty() instead."""
import warnings
warnings.warn("Use pty.openpty() instead.", DeprecationWarning, stacklevel=2) # Remove API in 3.14
slave_fd = os.open(slave_name, os.O_RDWR)
try:
master_fd, slave_fd = os.openpty()
except (AttributeError, OSError):
from fcntl import ioctl, I_PUSH
except ImportError:
return master_fd, slave_fd
try:
ioctl(slave_fd, I_PUSH, "ptem")
ioctl(slave_fd, I_PUSH, "ldterm")
except OSError:
pass
else:
slave_name = os.ttyname(slave_fd)
os.close(slave_fd)
return master_fd, slave_name
return _open_terminal()
return master_fd, slave_fd
def _open_terminal():
"""Open pty master and return (master_fd, tty_name)."""
@@ -66,26 +57,6 @@ def _open_terminal():
return (fd, '/dev/tty' + x + y)
raise OSError('out of pty devices')
def slave_open(tty_name):
"""slave_open(tty_name) -> slave_fd
Open the pty slave and acquire the controlling terminal, returning
opened filedescriptor.
Deprecated, use openpty() instead."""
import warnings
warnings.warn("Use pty.openpty() instead.", DeprecationWarning, stacklevel=2) # Remove API in 3.14
result = os.open(tty_name, os.O_RDWR)
try:
from fcntl import ioctl, I_PUSH
except ImportError:
return result
try:
ioctl(result, I_PUSH, "ptem")
ioctl(result, I_PUSH, "ldterm")
except OSError:
pass
return result
def fork():
"""fork() -> (pid, master_fd)

View File

@@ -1,4 +1,4 @@
# Autogenerated by Sphinx on Tue Feb 3 17:32:13 2026
# Autogenerated by Sphinx on Sun May 10 13:21:26 2026
# as part of the release process.
module_docs = {

File diff suppressed because it is too large Load Diff

17
Lib/random.py vendored
View File

@@ -836,7 +836,11 @@ class Random(_random.Random):
if not c:
return x
while True:
y += _floor(_log2(random()) / c) + 1
try:
y += _floor(_log2(random()) / c) + 1
except ValueError:
# Reject case where random() returned 0.0
continue
if y > n:
return x
x += 1
@@ -844,8 +848,8 @@ class Random(_random.Random):
# BTRS: Transformed rejection with squeeze method by Wolfgang Hörmann
# https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.47.8407&rep=rep1&type=pdf
assert n*p >= 10.0 and p <= 0.5
setup_complete = False
setup_complete = False
spq = _sqrt(n * p * (1.0 - p)) # Standard deviation of the distribution
b = 1.15 + 2.53 * spq
a = -0.0873 + 0.0248 * b + 0.01 * p
@@ -860,22 +864,23 @@ class Random(_random.Random):
k = _floor((2.0 * a / us + b) * u + c)
if k < 0 or k > n:
continue
v = random()
# The early-out "squeeze" test substantially reduces
# the number of acceptance condition evaluations.
v = random()
if us >= 0.07 and v <= vr:
return k
# Acceptance-rejection test.
# Note, the original paper erroneously omits the call to log(v)
# when comparing to the log of the rescaled binomial distribution.
if not setup_complete:
alpha = (2.83 + 5.1 / b) * spq
lpq = _log(p / (1.0 - p))
m = _floor((n + 1) * p) # Mode of the distribution
h = _lgamma(m + 1) + _lgamma(n - m + 1)
setup_complete = True # Only needs to be done once
# Acceptance-rejection test.
# Note, the original paper erroneously omits the call to log(v)
# when comparing to the log of the rescaled binomial distribution.
v *= alpha / (a / (us * us) + b)
if _log(v) <= h - _lgamma(k + 1) - _lgamma(n - k + 1) + (k - m) * lpq:
return k

25
Lib/runpy.py vendored
View File

@@ -103,8 +103,10 @@ def _run_module_code(code, init_globals=None,
# Helper to get the full name, spec and code for a module
def _get_module_details(mod_name, error=ImportError):
# name= is only accepted by ImportError and its subclasses.
kwargs = {"name": mod_name} if issubclass(error, ImportError) else {}
if mod_name.startswith("."):
raise error("Relative module names not supported")
raise error("Relative module names not supported", **kwargs)
pkg_name, _, _ = mod_name.rpartition(".")
if pkg_name:
# Try importing the parent to avoid catching initialization errors
@@ -137,12 +139,13 @@ def _get_module_details(mod_name, error=ImportError):
if mod_name.endswith(".py"):
msg += (f". Try using '{mod_name[:-3]}' instead of "
f"'{mod_name}' as the module name.")
raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex
raise error(msg.format(mod_name, type(ex).__name__, ex),
**kwargs) from ex
if spec is None:
raise error("No module named %s" % mod_name)
raise error("No module named %s" % mod_name, **kwargs)
if spec.submodule_search_locations is not None:
if mod_name == "__main__" or mod_name.endswith(".__main__"):
raise error("Cannot use package as __main__ module")
raise error("Cannot use package as __main__ module", **kwargs)
try:
pkg_main_name = mod_name + ".__main__"
return _get_module_details(pkg_main_name, error)
@@ -150,17 +153,19 @@ def _get_module_details(mod_name, error=ImportError):
if mod_name not in sys.modules:
raise # No module loaded; being a package is irrelevant
raise error(("%s; %r is a package and cannot " +
"be directly executed") %(e, mod_name))
"be directly executed") %(e, mod_name),
**kwargs)
loader = spec.loader
if loader is None:
raise error("%r is a namespace package and cannot be executed"
% mod_name)
% mod_name,
**kwargs)
try:
code = loader.get_code(mod_name)
except ImportError as e:
raise error(format(e)) from e
raise error(format(e), **kwargs) from e
if code is None:
raise error("No code object available for %s" % mod_name)
raise error("No code object available for %s" % mod_name, **kwargs)
return mod_name, spec, code
class _Error(Exception):
@@ -234,6 +239,7 @@ def _get_main_module_details(error=ImportError):
# Also moves the standard __main__ out of the way so that the
# preexisting __loader__ entry doesn't cause issues
main_name = "__main__"
kwargs = {"name": main_name} if issubclass(error, ImportError) else {}
saved_main = sys.modules[main_name]
del sys.modules[main_name]
try:
@@ -241,7 +247,8 @@ def _get_main_module_details(error=ImportError):
except ImportError as exc:
if main_name in str(exc):
raise error("can't find %r module in %r" %
(main_name, sys.path[0])) from exc
(main_name, sys.path[0]),
**kwargs) from exc
raise
finally:
sys.modules[main_name] = saved_main

24
Lib/shutil.py vendored
View File

@@ -1314,27 +1314,9 @@ def _unpack_zipfile(filename, extract_dir):
if not zipfile.is_zipfile(filename):
raise ReadError("%s is not a zip file" % filename)
zip = zipfile.ZipFile(filename)
try:
for info in zip.infolist():
name = info.filename
# don't extract absolute paths or ones with .. in them
if name.startswith('/') or '..' in name:
continue
targetpath = os.path.join(extract_dir, *name.split('/'))
if not targetpath:
continue
_ensure_directory(targetpath)
if not name.endswith('/'):
# file
with zip.open(name, 'r') as source, \
open(targetpath, 'wb') as target:
copyfileobj(source, target)
finally:
zip.close()
with zipfile.ZipFile(filename) as zip:
zip._ignore_invalid_names = True
zip.extractall(extract_dir)
def _unpack_tarfile(filename, extract_dir, *, filter=None):
"""Unpack tar/tar.gz/tar.bz2/tar.xz/tar.zst `filename` to `extract_dir`

2
Lib/site.py vendored
View File

@@ -290,7 +290,7 @@ def check_enableusersite():
# Copy of sysconfig._get_implementation()
def _get_implementation():
return 'RustPython' # XXX: RustPython; for site-packages
return 'RustPython' # XXX: RUSTPYTHON; for site-packages
# Copy of sysconfig._getuserbase()
def _getuserbase():

2
Lib/smtplib.py vendored
View File

@@ -251,7 +251,6 @@ class SMTP:
will be used.
"""
self._host = host
self.timeout = timeout
self.esmtp_features = {}
self.command_encoding = 'ascii'
@@ -342,6 +341,7 @@ class SMTP:
port = int(port)
except ValueError:
raise OSError("nonnumeric port")
self._host = host
if not port:
port = self.default_port
sys.audit("smtplib.connect", self, host, port)

28
Lib/socket.py vendored
View File

@@ -640,18 +640,22 @@ def _fallback_socketpair(family=AF_INET, type=SOCK_STREAM, proto=0):
# Authenticating avoids using a connection from something else
# able to connect to {host}:{port} instead of us.
# We expect only AF_INET and AF_INET6 families.
try:
if (
ssock.getsockname() != csock.getpeername()
or csock.getsockname() != ssock.getpeername()
):
raise ConnectionError("Unexpected peer connection")
except:
# getsockname() and getpeername() can fail
# if either socket isn't connected.
ssock.close()
csock.close()
raise
#
# Note that we skip this on WASI because on that platorm the client socket
# may not have finished connecting by the time we've reached this point (gh-146139).
if sys.platform != "wasi":
try:
if (
ssock.getsockname() != csock.getpeername()
or csock.getsockname() != ssock.getpeername()
):
raise ConnectionError("Unexpected peer connection")
except:
# getsockname() and getpeername() can fail
# if either socket isn't connected.
ssock.close()
csock.close()
raise
return (ssock, csock)

19
Lib/subprocess.py vendored
View File

@@ -351,15 +351,16 @@ def _args_from_interpreter_flags():
# -X options
if dev_mode:
args.extend(('-X', 'dev'))
for opt in ('faulthandler', 'tracemalloc', 'importtime',
'frozen_modules', 'showrefcount', 'utf8', 'gil'):
if opt in xoptions:
value = xoptions[opt]
if value is True:
arg = opt
else:
arg = '%s=%s' % (opt, value)
args.extend(('-X', arg))
for opt in sorted(xoptions):
if opt == 'dev':
# handled above via sys.flags.dev_mode
continue
value = xoptions[opt]
if value is True:
arg = opt
else:
arg = '%s=%s' % (opt, value)
args.extend(('-X', arg))
return args

View File

@@ -107,7 +107,7 @@ else:
_INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv']
def _get_implementation():
return 'RustPython' # XXX: For site-packages
return 'RustPython' # XXX: RUSTPYTHON; For site-packages
# NOTE: site.py has copy of this function.
# Sync it when modify this function.
@@ -698,11 +698,19 @@ def get_platform():
release = get_config_var("ANDROID_API_LEVEL")
# Wheel tags use the ABI names from Android's own tools.
# When Python is running on 32-bit ARM Android on a 64-bit ARM kernel,
# 'os.uname().machine' is 'armv8l'. Such devices run the same userspace
# code as 'armv7l' devices.
# During the build process of the Android testbed when targeting 32-bit ARM,
# '_PYTHON_HOST_PLATFORM' is 'arm-linux-androideabi', so 'machine' becomes
# 'arm'.
machine = {
"x86_64": "x86_64",
"i686": "x86",
"aarch64": "arm64_v8a",
"arm": "armeabi_v7a",
"armv7l": "armeabi_v7a",
"armv8l": "armeabi_v7a",
"i686": "x86",
"x86_64": "x86_64",
}[machine]
elif osname == "linux":
# At least on Linux/Intel, 'machine' is the processor --

29
Lib/tarfile.py vendored
View File

@@ -1278,6 +1278,20 @@ class TarInfo(object):
@classmethod
def frombuf(cls, buf, encoding, errors):
"""Construct a TarInfo object from a 512 byte bytes object.
To support the old v7 tar format AREGTYPE headers are
transformed to DIRTYPE headers if their name ends in '/'.
"""
return cls._frombuf(buf, encoding, errors)
@classmethod
def _frombuf(cls, buf, encoding, errors, *, dircheck=True):
"""Construct a TarInfo object from a 512 byte bytes object.
If ``dircheck`` is set to ``True`` then ``AREGTYPE`` headers will
be normalized to ``DIRTYPE`` if the name ends in a trailing slash.
``dircheck`` must be set to ``False`` if this function is called
on a follow-up header such as ``GNUTYPE_LONGNAME``.
"""
if len(buf) == 0:
raise EmptyHeaderError("empty header")
@@ -1308,7 +1322,7 @@ class TarInfo(object):
# Old V7 tar format represents a directory as a regular
# file with a trailing slash.
if obj.type == AREGTYPE and obj.name.endswith("/"):
if dircheck and obj.type == AREGTYPE and obj.name.endswith("/"):
obj.type = DIRTYPE
# The old GNU sparse format occupies some of the unused
@@ -1343,8 +1357,15 @@ class TarInfo(object):
"""Return the next TarInfo object from TarFile object
tarfile.
"""
return cls._fromtarfile(tarfile)
@classmethod
def _fromtarfile(cls, tarfile, *, dircheck=True):
"""
See dircheck documentation in _frombuf().
"""
buf = tarfile.fileobj.read(BLOCKSIZE)
obj = cls.frombuf(buf, tarfile.encoding, tarfile.errors)
obj = cls._frombuf(buf, tarfile.encoding, tarfile.errors, dircheck=dircheck)
obj.offset = tarfile.fileobj.tell() - BLOCKSIZE
return obj._proc_member(tarfile)
@@ -1402,7 +1423,7 @@ class TarInfo(object):
# Fetch the next header and process it.
try:
next = self.fromtarfile(tarfile)
next = self._fromtarfile(tarfile, dircheck=False)
except HeaderError as e:
raise SubsequentHeaderError(str(e)) from None
@@ -1537,7 +1558,7 @@ class TarInfo(object):
# Fetch the next header.
try:
next = self.fromtarfile(tarfile)
next = self._fromtarfile(tarfile, dircheck=False)
except HeaderError as e:
raise SubsequentHeaderError(str(e)) from None

36
Lib/tempfile.py vendored
View File

@@ -57,10 +57,11 @@ _bin_openflags = _text_openflags
if hasattr(_os, 'O_BINARY'):
_bin_openflags |= _os.O_BINARY
if hasattr(_os, 'TMP_MAX'):
TMP_MAX = _os.TMP_MAX
else:
TMP_MAX = 10000
# This is more than enough.
# Each name contains over 40 random bits. Even with a million temporary
# files, the chance of a conflict is less than 1 in a million, and with
# 20 attempts, it is less than 1e-120.
TMP_MAX = 20
# This variable _was_ unused for legacy reasons, see issue 10354.
# But as of 3.5 we actually use it at runtime so changing it would
@@ -196,8 +197,7 @@ def _get_default_tempdir(dirlist=None):
for dir in dirlist:
if dir != _os.curdir:
dir = _os.path.abspath(dir)
# Try only a few names per directory.
for seq in range(100):
for seq in range(TMP_MAX):
name = next(namer)
filename = _os.path.join(dir, name)
try:
@@ -213,10 +213,8 @@ def _get_default_tempdir(dirlist=None):
except FileExistsError:
pass
except PermissionError:
# This exception is thrown when a directory with the chosen name
# already exists on windows.
if (_os.name == 'nt' and _os.path.isdir(dir) and
_os.access(dir, _os.W_OK)):
# See the comment in mkdtemp().
if _os.name == 'nt' and _os.path.isdir(dir):
continue
break # no point trying more names in this directory
except OSError:
@@ -258,10 +256,8 @@ def _mkstemp_inner(dir, pre, suf, flags, output_type):
except FileExistsError:
continue # try again
except PermissionError:
# This exception is thrown when a directory with the chosen name
# already exists on windows.
if (_os.name == 'nt' and _os.path.isdir(dir) and
_os.access(dir, _os.W_OK)):
# See the comment in mkdtemp().
if _os.name == 'nt' and _os.path.isdir(dir) and seq < TMP_MAX - 1:
continue
else:
raise
@@ -386,10 +382,14 @@ def mkdtemp(suffix=None, prefix=None, dir=None):
except FileExistsError:
continue # try again
except PermissionError:
# This exception is thrown when a directory with the chosen name
# already exists on windows.
if (_os.name == 'nt' and _os.path.isdir(dir) and
_os.access(dir, _os.W_OK)):
# On Posix, this exception is raised when the user has no
# write access to the parent directory.
# On Windows, it is also raised when a directory with
# the chosen name already exists, or if the parent directory
# is not a directory.
# We cannot distinguish between "directory-exists-error" and
# "access-denied-error".
if _os.name == 'nt' and _os.path.isdir(dir) and seq < TMP_MAX - 1:
continue
else:
raise

17035
Lib/test/NormalizationTest-3.2.0.txt vendored Normal file

File diff suppressed because it is too large Load Diff

681
Lib/test/audit-tests.py vendored Normal file
View File

@@ -0,0 +1,681 @@
"""This script contains the actual auditing tests.
It should not be imported directly, but should be run by the test_audit
module with arguments identifying each test.
"""
import contextlib
import os
import sys
class TestHook:
"""Used in standard hook tests to collect any logged events.
Should be used in a with block to ensure that it has no impact
after the test completes.
"""
def __init__(self, raise_on_events=None, exc_type=RuntimeError):
self.raise_on_events = raise_on_events or ()
self.exc_type = exc_type
self.seen = []
self.closed = False
def __enter__(self, *a):
sys.addaudithook(self)
return self
def __exit__(self, *a):
self.close()
def close(self):
self.closed = True
@property
def seen_events(self):
return [i[0] for i in self.seen]
def __call__(self, event, args):
if self.closed:
return
self.seen.append((event, args))
if event in self.raise_on_events:
raise self.exc_type("saw event " + event)
# Simple helpers, since we are not in unittest here
def assertEqual(x, y):
if x != y:
raise AssertionError(f"{x!r} should equal {y!r}")
def assertIn(el, series):
if el not in series:
raise AssertionError(f"{el!r} should be in {series!r}")
def assertNotIn(el, series):
if el in series:
raise AssertionError(f"{el!r} should not be in {series!r}")
def assertSequenceEqual(x, y):
if len(x) != len(y):
raise AssertionError(f"{x!r} should equal {y!r}")
if any(ix != iy for ix, iy in zip(x, y)):
raise AssertionError(f"{x!r} should equal {y!r}")
@contextlib.contextmanager
def assertRaises(ex_type):
try:
yield
assert False, f"expected {ex_type}"
except BaseException as ex:
if isinstance(ex, AssertionError):
raise
assert type(ex) is ex_type, f"{ex} should be {ex_type}"
def test_basic():
with TestHook() as hook:
sys.audit("test_event", 1, 2, 3)
assertEqual(hook.seen[0][0], "test_event")
assertEqual(hook.seen[0][1], (1, 2, 3))
def test_block_add_hook():
# Raising an exception should prevent a new hook from being added,
# but will not propagate out.
with TestHook(raise_on_events="sys.addaudithook") as hook1:
with TestHook() as hook2:
sys.audit("test_event")
assertIn("test_event", hook1.seen_events)
assertNotIn("test_event", hook2.seen_events)
def test_block_add_hook_baseexception():
# Raising BaseException will propagate out when adding a hook
with assertRaises(BaseException):
with TestHook(
raise_on_events="sys.addaudithook", exc_type=BaseException
) as hook1:
# Adding this next hook should raise BaseException
with TestHook() as hook2:
pass
def test_marshal():
import marshal
o = ("a", "b", "c", 1, 2, 3)
payload = marshal.dumps(o)
with TestHook() as hook:
assertEqual(o, marshal.loads(marshal.dumps(o)))
try:
with open("test-marshal.bin", "wb") as f:
marshal.dump(o, f)
with open("test-marshal.bin", "rb") as f:
assertEqual(o, marshal.load(f))
finally:
os.unlink("test-marshal.bin")
actual = [(a[0], a[1]) for e, a in hook.seen if e == "marshal.dumps"]
assertSequenceEqual(actual, [(o, marshal.version)] * 2)
actual = [a[0] for e, a in hook.seen if e == "marshal.loads"]
assertSequenceEqual(actual, [payload])
actual = [e for e, a in hook.seen if e == "marshal.load"]
assertSequenceEqual(actual, ["marshal.load"])
def test_pickle():
import pickle
class PicklePrint:
def __reduce_ex__(self, p):
return str, ("Pwned!",)
payload_1 = pickle.dumps(PicklePrint())
payload_2 = pickle.dumps(("a", "b", "c", 1, 2, 3))
# Before we add the hook, ensure our malicious pickle loads
assertEqual("Pwned!", pickle.loads(payload_1))
with TestHook(raise_on_events="pickle.find_class") as hook:
with assertRaises(RuntimeError):
# With the hook enabled, loading globals is not allowed
pickle.loads(payload_1)
# pickles with no globals are okay
pickle.loads(payload_2)
def test_monkeypatch():
class A:
pass
class B:
pass
class C(A):
pass
a = A()
with TestHook() as hook:
# Catch name changes
C.__name__ = "X"
# Catch type changes
C.__bases__ = (B,)
# Ensure bypassing __setattr__ is still caught
type.__dict__["__bases__"].__set__(C, (B,))
# Catch attribute replacement
C.__init__ = B.__init__
# Catch attribute addition
C.new_attr = 123
# Catch class changes
a.__class__ = B
actual = [(a[0], a[1]) for e, a in hook.seen if e == "object.__setattr__"]
assertSequenceEqual(
[(C, "__name__"), (C, "__bases__"), (C, "__bases__"), (a, "__class__")], actual
)
def test_open(testfn):
# SSLContext.load_dh_params uses Py_fopen() rather than normal open()
try:
import ssl
load_dh_params = ssl.create_default_context().load_dh_params
except ImportError:
load_dh_params = None
try:
import readline
except ImportError:
readline = None
def rl(name):
if readline:
return getattr(readline, name, None)
else:
return None
# Try a range of "open" functions.
# All of them should fail
with TestHook(raise_on_events={"open"}) as hook:
for fn, *args in [
(open, testfn, "r"),
(open, sys.executable, "rb"),
(open, 3, "wb"),
(open, testfn, "w", -1, None, None, None, False, lambda *a: 1),
(load_dh_params, testfn),
(rl("read_history_file"), testfn),
(rl("read_history_file"), None),
(rl("write_history_file"), testfn),
(rl("write_history_file"), None),
(rl("append_history_file"), 0, testfn),
(rl("append_history_file"), 0, None),
(rl("read_init_file"), testfn),
(rl("read_init_file"), None),
]:
if not fn:
continue
with assertRaises(RuntimeError):
try:
fn(*args)
except NotImplementedError:
if fn == load_dh_params:
# Not callable in some builds
load_dh_params = None
raise RuntimeError
else:
raise
actual_mode = [(a[0], a[1]) for e, a in hook.seen if e == "open" and a[1]]
actual_flag = [(a[0], a[2]) for e, a in hook.seen if e == "open" and not a[1]]
assertSequenceEqual(
[
i
for i in [
(testfn, "r"),
(sys.executable, "r"),
(3, "w"),
(testfn, "w"),
(testfn, "rb") if load_dh_params else None,
(testfn, "r") if readline else None,
("~/.history", "r") if readline else None,
(testfn, "w") if readline else None,
("~/.history", "w") if readline else None,
(testfn, "a") if rl("append_history_file") else None,
("~/.history", "a") if rl("append_history_file") else None,
(testfn, "r") if readline else None,
("<readline_init_file>", "r") if readline else None,
]
if i is not None
],
actual_mode,
)
assertSequenceEqual([], actual_flag)
def test_cantrace():
traced = []
def trace(frame, event, *args):
if frame.f_code == TestHook.__call__.__code__:
traced.append(event)
old = sys.settrace(trace)
try:
with TestHook() as hook:
# No traced call
eval("1")
# No traced call
hook.__cantrace__ = False
eval("2")
# One traced call
hook.__cantrace__ = True
eval("3")
# Two traced calls (writing to private member, eval)
hook.__cantrace__ = 1
eval("4")
# One traced call (writing to private member)
hook.__cantrace__ = 0
finally:
sys.settrace(old)
assertSequenceEqual(["call"] * 4, traced)
def test_mmap():
import mmap
with TestHook() as hook:
mmap.mmap(-1, 8)
assertEqual(hook.seen[0][1][:2], (-1, 8))
def test_ctypes_call_function():
import ctypes
import _ctypes
with TestHook() as hook:
_ctypes.call_function(ctypes._memmove_addr, (0, 0, 0))
assert ("ctypes.call_function", (ctypes._memmove_addr, (0, 0, 0))) in hook.seen, f"{ctypes._memmove_addr=} {hook.seen=}"
ctypes.CFUNCTYPE(ctypes.c_voidp)(ctypes._memset_addr)(1, 0, 0)
assert ("ctypes.call_function", (ctypes._memset_addr, (1, 0, 0))) in hook.seen, f"{ctypes._memset_addr=} {hook.seen=}"
with TestHook() as hook:
ctypes.cast(ctypes.c_voidp(0), ctypes.POINTER(ctypes.c_char))
assert "ctypes.call_function" in hook.seen_events
with TestHook() as hook:
ctypes.string_at(id("ctypes.string_at") + 40)
assert "ctypes.call_function" in hook.seen_events
assert "ctypes.string_at" in hook.seen_events
def test_posixsubprocess():
import multiprocessing.util
exe = b"xxx"
args = [b"yyy", b"zzz"]
with TestHook() as hook:
multiprocessing.util.spawnv_passfds(exe, args, ())
assert ("_posixsubprocess.fork_exec", ([exe], args, None)) in hook.seen
def test_excepthook():
def excepthook(exc_type, exc_value, exc_tb):
if exc_type is not RuntimeError:
sys.__excepthook__(exc_type, exc_value, exc_tb)
def hook(event, args):
if event == "sys.excepthook":
if not isinstance(args[2], args[1]):
raise TypeError(f"Expected isinstance({args[2]!r}, " f"{args[1]!r})")
if args[0] != excepthook:
raise ValueError(f"Expected {args[0]} == {excepthook}")
print(event, repr(args[2]))
sys.addaudithook(hook)
sys.excepthook = excepthook
raise RuntimeError("fatal-error")
def test_unraisablehook():
from _testcapi import err_formatunraisable
def unraisablehook(hookargs):
pass
def hook(event, args):
if event == "sys.unraisablehook":
if args[0] != unraisablehook:
raise ValueError(f"Expected {args[0]} == {unraisablehook}")
print(event, repr(args[1].exc_value), args[1].err_msg)
sys.addaudithook(hook)
sys.unraisablehook = unraisablehook
err_formatunraisable(RuntimeError("nonfatal-error"),
"Exception ignored for audit hook test")
def test_winreg():
from winreg import OpenKey, EnumKey, CloseKey, HKEY_LOCAL_MACHINE
def hook(event, args):
if not event.startswith("winreg."):
return
print(event, *args)
sys.addaudithook(hook)
k = OpenKey(HKEY_LOCAL_MACHINE, "Software")
EnumKey(k, 0)
try:
EnumKey(k, 10000)
except OSError:
pass
else:
raise RuntimeError("Expected EnumKey(HKLM, 10000) to fail")
kv = k.Detach()
CloseKey(kv)
def test_socket():
import socket
def hook(event, args):
if event.startswith("socket."):
print(event, *args)
sys.addaudithook(hook)
socket.gethostname()
# Don't care if this fails, we just want the audit message
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# Don't care if this fails, we just want the audit message
sock.bind(('127.0.0.1', 8080))
except Exception:
pass
finally:
sock.close()
def test_gc():
import gc
def hook(event, args):
if event.startswith("gc."):
print(event, *args)
sys.addaudithook(hook)
gc.get_objects(generation=1)
x = object()
y = [x]
gc.get_referrers(x)
gc.get_referents(y)
def test_http_client():
import http.client
def hook(event, args):
if event.startswith("http.client."):
print(event, *args[1:])
sys.addaudithook(hook)
conn = http.client.HTTPConnection('www.python.org')
try:
conn.request('GET', '/')
except OSError:
print('http.client.send', '[cannot send]')
finally:
conn.close()
def test_sqlite3():
import sqlite3
def hook(event, *args):
if event.startswith("sqlite3."):
print(event, *args)
sys.addaudithook(hook)
cx1 = sqlite3.connect(":memory:")
cx2 = sqlite3.Connection(":memory:")
# Configured without --enable-loadable-sqlite-extensions
try:
if hasattr(sqlite3.Connection, "enable_load_extension"):
cx1.enable_load_extension(False)
try:
cx1.load_extension("test")
except sqlite3.OperationalError:
pass
else:
raise RuntimeError("Expected sqlite3.load_extension to fail")
finally:
cx1.close()
cx2.close()
def test_sys_getframe():
import sys
def hook(event, args):
if event.startswith("sys."):
print(event, args[0].f_code.co_name)
sys.addaudithook(hook)
sys._getframe()
def test_sys_getframemodulename():
import sys
def hook(event, args):
if event.startswith("sys."):
print(event, *args)
sys.addaudithook(hook)
sys._getframemodulename()
def test_threading():
import _thread
def hook(event, args):
if event.startswith(("_thread.", "cpython.PyThreadState", "test.")):
print(event, args)
sys.addaudithook(hook)
lock = _thread.allocate_lock()
lock.acquire()
class test_func:
def __repr__(self): return "<test_func>"
def __call__(self):
sys.audit("test.test_func")
lock.release()
i = _thread.start_new_thread(test_func(), ())
lock.acquire()
handle = _thread.start_joinable_thread(test_func())
handle.join()
def test_threading_abort():
# Ensures that aborting PyThreadState_New raises the correct exception
import _thread
class ThreadNewAbortError(Exception):
pass
def hook(event, args):
if event == "cpython.PyThreadState_New":
raise ThreadNewAbortError()
sys.addaudithook(hook)
try:
_thread.start_new_thread(lambda: None, ())
except ThreadNewAbortError:
# Other exceptions are raised and the test will fail
pass
def test_wmi_exec_query():
import _wmi
def hook(event, args):
if event.startswith("_wmi."):
print(event, args[0])
sys.addaudithook(hook)
try:
_wmi.exec_query("SELECT * FROM Win32_OperatingSystem")
except WindowsError as e:
# gh-112278: WMI may be slow response when first called, but we still
# get the audit event, so just ignore the timeout
if e.winerror != 258:
raise
def test_syslog():
import syslog
def hook(event, args):
if event.startswith("syslog."):
print(event, *args)
sys.addaudithook(hook)
syslog.openlog('python')
syslog.syslog('test')
syslog.setlogmask(syslog.LOG_DEBUG)
syslog.closelog()
# implicit open
syslog.syslog('test2')
# open with default ident
syslog.openlog(logoption=syslog.LOG_NDELAY, facility=syslog.LOG_LOCAL0)
sys.argv = None
syslog.openlog()
syslog.closelog()
def test_not_in_gc():
import gc
hook = lambda *a: None
sys.addaudithook(hook)
for o in gc.get_objects():
if isinstance(o, list):
assert hook not in o
def test_time(mode):
import time
def hook(event, args):
if event.startswith("time."):
if mode == 'print':
print(event, *args)
elif mode == 'fail':
raise AssertionError('hook failed')
sys.addaudithook(hook)
time.sleep(0)
time.sleep(0.0625) # 1/16, a small exact float
try:
time.sleep(-1)
except ValueError:
pass
def test_sys_monitoring_register_callback():
import sys
def hook(event, args):
if event.startswith("sys.monitoring"):
print(event, args)
sys.addaudithook(hook)
sys.monitoring.register_callback(1, 1, None)
def test_winapi_createnamedpipe(pipe_name):
import _winapi
def hook(event, args):
if event == "_winapi.CreateNamedPipe":
print(event, args)
sys.addaudithook(hook)
_winapi.CreateNamedPipe(pipe_name, _winapi.PIPE_ACCESS_DUPLEX, 8, 2, 0, 0, 0, 0)
def test_assert_unicode():
import sys
sys.addaudithook(lambda *args: None)
try:
sys.audit(9)
except TypeError:
pass
else:
raise RuntimeError("Expected sys.audit(9) to fail.")
def test_sys_remote_exec():
import tempfile
pid = os.getpid()
event_pid = -1
event_script_path = ""
remote_event_script_path = ""
def hook(event, args):
if event not in ["sys.remote_exec", "cpython.remote_debugger_script"]:
return
print(event, args)
match event:
case "sys.remote_exec":
nonlocal event_pid, event_script_path
event_pid = args[0]
event_script_path = args[1]
case "cpython.remote_debugger_script":
nonlocal remote_event_script_path
remote_event_script_path = args[0]
sys.addaudithook(hook)
with tempfile.NamedTemporaryFile(mode='w+', delete=True) as tmp_file:
tmp_file.write("a = 1+1\n")
tmp_file.flush()
sys.remote_exec(pid, tmp_file.name)
assertEqual(event_pid, pid)
assertEqual(event_script_path, tmp_file.name)
assertEqual(remote_event_script_path, tmp_file.name)
if __name__ == "__main__":
from test.support import suppress_msvcrt_asserts
suppress_msvcrt_asserts()
test = sys.argv[1]
globals()[test](*sys.argv[2:])

View File

@@ -47,7 +47,11 @@ import _strptime
try:
import _pydatetime
except ImportError:
pass
_pydatetime = None
try:
import _datetime
except ImportError:
_datetime = None
#
pickle_loads = {pickle.loads, pickle._loads}
@@ -3011,7 +3015,6 @@ class TestDateTime(TestDate):
self.assertEqual(t.strftime("%z"), "-0200" + z)
self.assertEqual(t.strftime("%:z"), "-02:00:" + z)
@unittest.skip("TODO: RUSTPYTHON")
def test_strftime_special(self):
t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
s1 = t.strftime('%c')
@@ -3879,7 +3882,6 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
# gh-85432: The parameter was named "fmt" in the pure-Python impl.
t.strftime(format="%f")
@unittest.skip("TODO: RUSTPYTHON")
def test_strftime_special(self):
t = self.theclass(1, 2, 3, 4)
s1 = t.strftime('%I%p%Z')
@@ -4360,7 +4362,6 @@ class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
self.assertEqual(t.microsecond, 0)
self.assertIsNone(t.tzinfo)
@unittest.skip("TODO: RUSTPYTHON")
def test_zones(self):
est = FixedOffset(-300, "EST", 1)
utc = FixedOffset(0, "UTC", -2)

View File

@@ -40,6 +40,7 @@ BaseException
├── ReferenceError
├── RuntimeError
│ ├── NotImplementedError
│ ├── PythonFinalizationError
│ └── RecursionError
├── StopAsyncIteration
├── StopIteration

390
Lib/test/picklecommon.py vendored Normal file
View File

@@ -0,0 +1,390 @@
# Classes used for pickle testing.
# They are moved to separate file, so they can be loaded
# in other Python version for test_xpickle.
import sys
class C:
def __eq__(self, other):
return self.__dict__ == other.__dict__
# For test_load_classic_instance
class D(C):
def __init__(self, arg):
pass
class E(C):
def __getinitargs__(self):
return ()
import __main__
__main__.C = C
C.__module__ = "__main__"
__main__.D = D
D.__module__ = "__main__"
__main__.E = E
E.__module__ = "__main__"
# Simple mutable object.
class Object(object):
pass
# Hashable immutable key object containing unheshable mutable data.
class K:
def __init__(self, value):
self.value = value
def __reduce__(self):
# Shouldn't support the recursion itself
return K, (self.value,)
class WithSlots(object):
__slots__ = ('a', 'b')
class WithSlotsSubclass(WithSlots):
__slots__ = ('c',)
class WithSlotsAndDict(object):
__slots__ = ('a', '__dict__')
class WithPrivateAttrs(object):
def __init__(self, a):
self.__private = a
def get(self):
return self.__private
class WithPrivateAttrsSubclass(WithPrivateAttrs):
def __init__(self, a, b):
super().__init__(a)
self.__private = b
def get2(self):
return self.__private
class WithPrivateSlots(object):
__slots__ = ('__private',)
def __init__(self, a):
self.__private = a
def get(self):
return self.__private
class WithPrivateSlotsSubclass(WithPrivateSlots):
__slots__ = ('__private',)
def __init__(self, a, b):
super().__init__(a)
self.__private = b
def get2(self):
return self.__private
# For test_misc
class myint(int):
def __init__(self, x):
self.str = str(x)
# For test_misc and test_getinitargs
class initarg(C):
def __init__(self, a, b):
self.a = a
self.b = b
def __getinitargs__(self):
return self.a, self.b
# For test_metaclass
class metaclass(type):
pass
if sys.version_info >= (3,):
# Syntax not compatible with Python 2
exec('''
class use_metaclass(object, metaclass=metaclass):
pass
''')
else:
class use_metaclass(object):
__metaclass__ = metaclass
# Test classes for reduce_ex
class R:
def __init__(self, reduce=None):
self.reduce = reduce
def __reduce__(self, proto):
return self.reduce
class REX:
def __init__(self, reduce_ex=None):
self.reduce_ex = reduce_ex
def __reduce_ex__(self, proto):
return self.reduce_ex
class REX_one(object):
"""No __reduce_ex__ here, but inheriting it from object"""
_reduce_called = 0
def __reduce__(self):
self._reduce_called = 1
return REX_one, ()
class REX_two(object):
"""No __reduce__ here, but inheriting it from object"""
_proto = None
def __reduce_ex__(self, proto):
self._proto = proto
return REX_two, ()
class REX_three(object):
_proto = None
def __reduce_ex__(self, proto):
self._proto = proto
return REX_two, ()
def __reduce__(self):
raise AssertionError("This __reduce__ shouldn't be called")
class REX_four(object):
"""Calling base class method should succeed"""
_proto = None
def __reduce_ex__(self, proto):
self._proto = proto
return object.__reduce_ex__(self, proto)
class REX_five(object):
"""This one used to fail with infinite recursion"""
_reduce_called = 0
def __reduce__(self):
self._reduce_called = 1
return object.__reduce__(self)
class REX_six(object):
"""This class is used to check the 4th argument (list iterator) of
the reduce protocol.
"""
def __init__(self, items=None):
self.items = items if items is not None else []
def __eq__(self, other):
return type(self) is type(other) and self.items == other.items
def append(self, item):
self.items.append(item)
def __reduce__(self):
return type(self), (), None, iter(self.items), None
class REX_seven(object):
"""This class is used to check the 5th argument (dict iterator) of
the reduce protocol.
"""
def __init__(self, table=None):
self.table = table if table is not None else {}
def __eq__(self, other):
return type(self) is type(other) and self.table == other.table
def __setitem__(self, key, value):
self.table[key] = value
def __reduce__(self):
return type(self), (), None, None, iter(self.table.items())
class REX_state(object):
"""This class is used to check the 3th argument (state) of
the reduce protocol.
"""
def __init__(self, state=None):
self.state = state
def __eq__(self, other):
return type(self) is type(other) and self.state == other.state
def __setstate__(self, state):
self.state = state
def __reduce__(self):
return type(self), (), self.state
# For test_reduce_ex_None
class REX_None:
""" Setting __reduce_ex__ to None should fail """
__reduce_ex__ = None
# For test_reduce_None
class R_None:
""" Setting __reduce__ to None should fail """
__reduce__ = None
# For test_pickle_setstate_None
class C_None_setstate:
""" Setting __setstate__ to None should fail """
def __getstate__(self):
return 1
__setstate__ = None
# Test classes for newobj
# For test_newobj_generic and test_newobj_proxies
class MyInt(int):
sample = 1
if sys.version_info >= (3,):
class MyLong(int):
sample = 1
else:
class MyLong(long):
sample = long(1)
class MyFloat(float):
sample = 1.0
class MyComplex(complex):
sample = 1.0 + 0.0j
class MyStr(str):
sample = "hello"
if sys.version_info >= (3,):
class MyUnicode(str):
sample = "hello \u1234"
else:
class MyUnicode(unicode):
sample = unicode(r"hello \u1234", "raw-unicode-escape")
class MyTuple(tuple):
sample = (1, 2, 3)
class MyList(list):
sample = [1, 2, 3]
class MyDict(dict):
sample = {"a": 1, "b": 2}
class MySet(set):
sample = {"a", "b"}
class MyFrozenSet(frozenset):
sample = frozenset({"a", "b"})
myclasses = [MyInt, MyLong, MyFloat,
MyComplex,
MyStr, MyUnicode,
MyTuple, MyList, MyDict, MySet, MyFrozenSet]
# For test_newobj_overridden_new
class MyIntWithNew(int):
def __new__(cls, value):
raise AssertionError
class MyIntWithNew2(MyIntWithNew):
__new__ = int.__new__
# For test_newobj_list_slots
class SlotList(MyList):
__slots__ = ["foo"]
# Ruff "redefined while unused" false positive here due to `global` variables
# being assigned (and then restored) from within test methods earlier in the file
class SimpleNewObj(int): # noqa: F811
def __init__(self, *args, **kwargs):
# raise an error, to make sure this isn't called
raise TypeError("SimpleNewObj.__init__() didn't expect to get called")
def __eq__(self, other):
return int(self) == int(other) and self.__dict__ == other.__dict__
class ComplexNewObj(SimpleNewObj):
def __getnewargs__(self):
return ('%X' % self, 16)
class ComplexNewObjEx(SimpleNewObj):
def __getnewargs_ex__(self):
return ('%X' % self,), {'base': 16}
class ZeroCopyBytes(bytes):
readonly = True
c_contiguous = True
f_contiguous = True
zero_copy_reconstruct = True
def __reduce_ex__(self, protocol):
if protocol >= 5:
import pickle
return type(self)._reconstruct, (pickle.PickleBuffer(self),), None
else:
return type(self)._reconstruct, (bytes(self),)
def __repr__(self):
return "{}({!r})".format(self.__class__.__name__, bytes(self))
__str__ = __repr__
@classmethod
def _reconstruct(cls, obj):
with memoryview(obj) as m:
obj = m.obj
if type(obj) is cls:
# Zero-copy
return obj
else:
return cls(obj)
class ZeroCopyBytearray(bytearray):
readonly = False
c_contiguous = True
f_contiguous = True
zero_copy_reconstruct = True
def __reduce_ex__(self, protocol):
if protocol >= 5:
import pickle
return type(self)._reconstruct, (pickle.PickleBuffer(self),), None
else:
return type(self)._reconstruct, (bytes(self),)
def __repr__(self):
return "{}({!r})".format(self.__class__.__name__, bytes(self))
__str__ = __repr__
@classmethod
def _reconstruct(cls, obj):
with memoryview(obj) as m:
obj = m.obj
if type(obj) is cls:
# Zero-copy
return obj
else:
return cls(obj)
# For test_nested_names
class Nested:
class A:
class B:
class C:
pass
# For test_py_methods
class PyMethodsTest:
@staticmethod
def cheese():
return "cheese"
@classmethod
def wine(cls):
assert cls is PyMethodsTest
return "wine"
def biscuits(self):
assert isinstance(self, PyMethodsTest)
return "biscuits"
class Nested:
"Nested class"
@staticmethod
def ketchup():
return "ketchup"
@classmethod
def maple(cls):
assert cls is PyMethodsTest.Nested
return "maple"
def pie(self):
assert isinstance(self, PyMethodsTest.Nested)
return "pie"
# For test_c_methods
class Subclass(tuple):
class Nested(str):
pass

1152
Lib/test/pickletester.py vendored

File diff suppressed because it is too large Load Diff

View File

@@ -531,7 +531,7 @@ xyzabc
(r'a[ ]*?\ (\d+).*', 'a 10', SUCCEED, 'found', 'a 10'),
(r'a[ ]*?\ (\d+).*', 'a 10', SUCCEED, 'found', 'a 10'),
# bug 127259: \Z shouldn't depend on multiline mode
(r'(?ms).*?x\s*\Z(.*)','xx\nx\n', SUCCEED, 'g1', ''),
(r'(?ms).*?x\s*\z(.*)','xx\nx\n', SUCCEED, 'g1', ''),
# bug 128899: uppercase literals under the ignorecase flag
(r'(?i)M+', 'MMM', SUCCEED, 'found', 'MMM'),
(r'(?i)m+', 'MMM', SUCCEED, 'found', 'MMM'),

33
Lib/test/seq_tests.py vendored
View File

@@ -261,23 +261,20 @@ class CommonTest(unittest.TestCase):
self.assertEqual(min(u), 0)
self.assertEqual(max(u), 2)
def test_addmul(self):
def test_add(self):
u1 = self.type2test([0])
u2 = self.type2test([0, 1])
self.assertEqual(u1, u1 + self.type2test())
self.assertEqual(u1, self.type2test() + u1)
self.assertEqual(u1 + self.type2test([1]), u2)
self.assertEqual(self.type2test([-1]) + u1, self.type2test([-1, 0]))
self.assertEqual(self.type2test(), u2*0)
self.assertEqual(self.type2test(), 0*u2)
def test_mul(self):
u2 = self.type2test([0, 1])
self.assertEqual(self.type2test(), u2*0)
self.assertEqual(self.type2test(), 0*u2)
self.assertEqual(u2, u2*1)
self.assertEqual(u2, 1*u2)
self.assertEqual(u2, u2*1)
self.assertEqual(u2, 1*u2)
self.assertEqual(u2+u2, u2*2)
self.assertEqual(u2+u2, 2*u2)
self.assertEqual(u2+u2, u2*2)
self.assertEqual(u2+u2, 2*u2)
self.assertEqual(u2+u2+u2, u2*3)
@@ -286,8 +283,9 @@ class CommonTest(unittest.TestCase):
class subclass(self.type2test):
pass
u3 = subclass([0, 1])
self.assertEqual(u3, u3*1)
self.assertIsNot(u3, u3*1)
r = u3*1
self.assertEqual(r, u3)
self.assertIsNot(r, u3)
def test_iadd(self):
u = self.type2test([0, 1])
@@ -348,6 +346,21 @@ class CommonTest(unittest.TestCase):
self.assertRaises(ValueError, a.__getitem__, slice(0, 10, 0))
self.assertRaises(TypeError, a.__getitem__, 'x')
def _assert_cmp(self, a, b, r):
self.assertIs(a == b, r == 0)
self.assertIs(a != b, r != 0)
self.assertIs(a > b, r > 0)
self.assertIs(a <= b, r <= 0)
self.assertIs(a < b, r < 0)
self.assertIs(a >= b, r >= 0)
def test_cmp(self):
a = self.type2test([0, 1])
self._assert_cmp(a, a, 0)
self._assert_cmp(a, self.type2test([0, 1]), 0)
self._assert_cmp(a, self.type2test([0]), 1)
self._assert_cmp(a, self.type2test([0, 2]), -1)
def test_count(self):
a = self.type2test([0, 1, 2])*3
self.assertEqual(a.count(0), 3)
@@ -426,7 +439,7 @@ class CommonTest(unittest.TestCase):
self.assertEqual(lst2, lst)
self.assertNotEqual(id(lst2), id(lst))
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skip("TODO: RUSTPYTHON; hangs")
def test_free_after_iterating(self):
support.check_free_after_iterating(self, iter, self.type2test)
support.check_free_after_iterating(self, reversed, self.type2test)

View File

@@ -90,6 +90,55 @@ class BaseTest:
args = self.fixtype(args)
getattr(obj, methodname)(*args)
def _get_teststrings(self, charset, digits):
base = len(charset)
teststrings = set()
for i in range(base ** digits):
entry = []
for j in range(digits):
i, m = divmod(i, base)
entry.append(charset[m])
teststrings.add(''.join(entry))
teststrings = [self.fixtype(ts) for ts in teststrings]
return teststrings
def test_add(self):
s = self.fixtype('ab')
self.assertEqual(s + self.fixtype(''), s)
self.assertEqual(self.fixtype('') + s, s)
self.assertEqual(s + self.fixtype('cd'), self.fixtype('abcd'))
def test_mul(self):
s = self.fixtype('ab')
self.assertEqual(s*0, self.fixtype(''))
self.assertEqual(0*s, self.fixtype(''))
self.assertEqual(s*1, s)
self.assertEqual(1*s, s)
self.assertEqual(s*2, self.fixtype('abab'))
self.assertEqual(2*s, self.fixtype('abab'))
class subclass(self.type2test):
pass
s = subclass(self.fixtype('ab'))
r = s*1
self.assertEqual(r, s)
self.assertIsNot(r, s)
def _assert_cmp(self, a, b, r):
self.assertIs(a == b, r == 0)
self.assertIs(a != b, r != 0)
self.assertIs(a > b, r > 0)
self.assertIs(a <= b, r <= 0)
self.assertIs(a < b, r < 0)
self.assertIs(a >= b, r >= 0)
def test_cmp(self):
a = self.fixtype('ab')
self._assert_cmp(a, a, 0)
self._assert_cmp(a, self.fixtype('ab'), 0)
self._assert_cmp(a, self.fixtype('a'), 1)
self._assert_cmp(a, self.fixtype('ac'), -1)
def test_count(self):
self.checkequal(3, 'aaa', 'count', 'a')
self.checkequal(0, 'aaa', 'count', 'b')
@@ -130,17 +179,7 @@ class BaseTest:
# For a variety of combinations,
# verify that str.count() matches an equivalent function
# replacing all occurrences and then differencing the string lengths
charset = ['', 'a', 'b']
digits = 7
base = len(charset)
teststrings = set()
for i in range(base ** digits):
entry = []
for j in range(digits):
i, m = divmod(i, base)
entry.append(charset[m])
teststrings.add(''.join(entry))
teststrings = [self.fixtype(ts) for ts in teststrings]
teststrings = self._get_teststrings(['', 'a', 'b'], 7)
for i in teststrings:
n = len(i)
for j in teststrings:
@@ -197,17 +236,7 @@ class BaseTest:
# For a variety of combinations,
# verify that str.find() matches __contains__
# and that the found substring is really at that location
charset = ['', 'a', 'b', 'c']
digits = 5
base = len(charset)
teststrings = set()
for i in range(base ** digits):
entry = []
for j in range(digits):
i, m = divmod(i, base)
entry.append(charset[m])
teststrings.add(''.join(entry))
teststrings = [self.fixtype(ts) for ts in teststrings]
teststrings = self._get_teststrings(['', 'a', 'b', 'c'], 5)
for i in teststrings:
for j in teststrings:
loc = i.find(j)
@@ -244,17 +273,7 @@ class BaseTest:
# For a variety of combinations,
# verify that str.rfind() matches __contains__
# and that the found substring is really at that location
charset = ['', 'a', 'b', 'c']
digits = 5
base = len(charset)
teststrings = set()
for i in range(base ** digits):
entry = []
for j in range(digits):
i, m = divmod(i, base)
entry.append(charset[m])
teststrings.add(''.join(entry))
teststrings = [self.fixtype(ts) for ts in teststrings]
teststrings = self._get_teststrings(['', 'a', 'b', 'c'], 5)
for i in teststrings:
for j in teststrings:
loc = i.rfind(j)
@@ -295,6 +314,19 @@ class BaseTest:
else:
self.checkraises(TypeError, 'hello', 'index', 42)
# For a variety of combinations,
# verify that str.index() matches __contains__
# and that the found substring is really at that location
teststrings = self._get_teststrings(['', 'a', 'b', 'c'], 5)
for i in teststrings:
for j in teststrings:
if j in i:
loc = i.index(j)
self.assertGreaterEqual(loc, 0)
self.assertEqual(i[loc:loc+len(j)], j)
else:
self.assertRaises(ValueError, i.index, j)
def test_rindex(self):
self.checkequal(12, 'abcdefghiabc', 'rindex', '')
self.checkequal(3, 'abcdefghiabc', 'rindex', 'def')
@@ -321,6 +353,19 @@ class BaseTest:
else:
self.checkraises(TypeError, 'hello', 'rindex', 42)
# For a variety of combinations,
# verify that str.rindex() matches __contains__
# and that the found substring is really at that location
teststrings = self._get_teststrings(['', 'a', 'b', 'c'], 5)
for i in teststrings:
for j in teststrings:
if j in i:
loc = i.rindex(j)
self.assertGreaterEqual(loc, 0)
self.assertEqual(i[loc:loc+len(j)], j)
else:
self.assertRaises(ValueError, i.rindex, j)
def test_find_periodic_pattern(self):
"""Cover the special path for periodic patterns."""
def reference_find(p, s):
@@ -437,8 +482,10 @@ class BaseTest:
self.checkequal(' a\n b', ' \ta\n\tb', 'expandtabs', 1)
self.checkraises(TypeError, 'hello', 'expandtabs', 42, 42)
# TODO: RUSTPYTHON; expandtabs overflow checks
# if sys.maxsize < (1 << 32) and struct.calcsize('P') == 4:
#
# This test is only valid when sizeof(int) == sizeof(void*) == 4.
# XXX RUSTPYTHON TODO: expandtabs overflow checks
if sys.maxsize < (1 << 32) and struct.calcsize('P') == 4 and False:
self.checkraises(OverflowError,
'\ta\n\tb', 'expandtabs', sys.maxsize)
@@ -768,6 +815,15 @@ class BaseTest:
self.checkraises(TypeError, 'hello', 'replace', 42, 'h')
self.checkraises(TypeError, 'hello', 'replace', 'h', 42)
def test_replacement_on_buffer_boundary(self):
# gh-127971: Check we don't read past the end of the buffer when a
# potential match misses on the last character.
any_3_nonblank_codepoints = '!!!'
seven_codepoints = any_3_nonblank_codepoints + ' ' + any_3_nonblank_codepoints
a = (' ' * 243) + seven_codepoints + (' ' * 7)
b = ' ' * 6 + chr(256)
a.replace(seven_codepoints, b)
def test_replace_uses_two_way_maxcount(self):
# Test that maxcount works in _two_way_count in fastsearch.h
A, B = "A"*1000, "B"*1000
@@ -780,7 +836,7 @@ class BaseTest:
self.checkequal(AABAA + "ccc",
AABAA + ABBA, 'replace', ABBA, "ccc", 2)
@unittest.skip("TODO: RUSTPYTHON, may only apply to 32-bit platforms")
@unittest.skip("TODO: RUSTPYTHON; may only apply to 32-bit platforms")
@unittest.skipIf(sys.maxsize > (1 << 32) or struct.calcsize('P') != 4,
'only applies to 32-bit platforms')
def test_replace_overflow(self):
@@ -1134,8 +1190,8 @@ class StringLikeTest(BaseTest):
self.checkequal('\u2160\u2171\u2172',
'\u2170\u2171\u2172', 'capitalize')
# check with Ll chars with no upper - nothing changes here
self.checkequal('\u019b\u1d00\u1d86\u0221\u1fb7',
'\u019b\u1d00\u1d86\u0221\u1fb7', 'capitalize')
self.checkequal('\u1d00\u1d86\u0221\u1fb7',
'\u1d00\u1d86\u0221\u1fb7', 'capitalize')
def test_startswith(self):
self.checkequal(True, 'hello', 'startswith', 'he')
@@ -1248,9 +1304,7 @@ class StringLikeTest(BaseTest):
self.checkequal(False, 'asd', '__contains__', 'asdf')
self.checkequal(False, '', '__contains__', 'asdf')
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_subscript(self):
self.checkequal('a', 'abc', '__getitem__', 0)
self.checkequal('c', 'abc', '__getitem__', -1)
@@ -1292,6 +1346,7 @@ class StringLikeTest(BaseTest):
slice(start, stop, step))
def test_mul(self):
super().test_mul()
self.checkequal('', 'abc', '__mul__', -1)
self.checkequal('', 'abc', '__mul__', 0)
self.checkequal('abc', 'abc', '__mul__', 1)
@@ -1504,8 +1559,7 @@ class StringLikeTest(BaseTest):
self.checkequal(True, s, 'startswith', 'h', None, -2)
self.checkequal(False, s, 'startswith', 'x', None, None)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_find_etc_raise_correct_error_messages(self):
# issue 11828
s = 'hello'

View File

@@ -548,7 +548,6 @@ def requires_lzma(reason='requires lzma'):
import lzma
except ImportError:
lzma = None
lzma = None # XXX: RUSTPYTHON; xz is not supported yet
return unittest.skipUnless(lzma, reason)
def requires_zstd(reason='requires zstd'):
@@ -856,8 +855,6 @@ def gc_collect():
longer than expected. This function tries its best to force all garbage
objects to disappear.
"""
return # TODO: RUSTPYTHON
import gc
gc.collect()
gc.collect()
@@ -865,13 +862,6 @@ def gc_collect():
@contextlib.contextmanager
def disable_gc():
# TODO: RUSTPYTHON; GC is not supported yet
try:
yield
finally:
pass
return
import gc
have_gc = gc.isenabled()
gc.disable()
@@ -2004,10 +1994,6 @@ def _check_tracemalloc():
def check_free_after_iterating(test, iter, cls, args=()):
# TODO: RUSTPYTHON; GC is not supported yet
test.assertTrue(False)
return
done = False
def wrapper():
class A(cls):
@@ -3048,6 +3034,10 @@ def get_signal_name(exitcode):
except KeyError:
pass
# Format Windows exit status as hexadecimal
if 0xC0000000 <= exitcode:
return f"0x{exitcode:X}"
return None
class BrokenIter:

View File

@@ -1,6 +1,6 @@
from enum import Enum
import functools
import unittest
from enum import Enum
__all__ = [
"given",

View File

@@ -1,6 +1,5 @@
import ast
class ASTTestMixin:
"""Test mixing to have basic assertions for AST nodes."""

View File

@@ -51,27 +51,17 @@ many of the difficult problems for you, making the task of building
sophisticated high-performance network servers and clients a snap.
"""
import os
import select
import socket
import sys
import time
import warnings
from errno import (
EAGAIN,
EALREADY,
EBADF,
ECONNABORTED,
ECONNRESET,
EINPROGRESS,
EINVAL,
EISCONN,
ENOTCONN,
EPIPE,
ESHUTDOWN,
EWOULDBLOCK,
errorcode,
)
import os
from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, EINVAL, \
ENOTCONN, ESHUTDOWN, EISCONN, EBADF, ECONNABORTED, EPIPE, EAGAIN, \
errorcode
_DISCONNECTED = frozenset({ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE,
EBADF})

View File

@@ -1,10 +1,9 @@
"""bytecode_helper - support tools for testing correct bytecode generation"""
import unittest
import dis
import io
import opcode
import unittest
try:
import _testinternalcapi
except ImportError:

View File

@@ -1,22 +1,18 @@
"""Cross-interpreter Channels High Level Module."""
import time
from concurrent.interpreters import _crossinterp
from concurrent.interpreters._crossinterp import (
UNBOUND_ERROR,
UNBOUND_REMOVE,
)
import _interpchannels as _channels
from concurrent.interpreters import _crossinterp
# aliases:
from _interpchannels import (
ChannelClosedError,
ChannelEmptyError,
ChannelError,
ChannelNotEmptyError,
ChannelNotFoundError,
ChannelError, ChannelNotFoundError, ChannelClosedError,
ChannelEmptyError, ChannelNotEmptyError,
)
from concurrent.interpreters._crossinterp import (
UNBOUND_ERROR, UNBOUND_REMOVE,
)
__all__ = [
'UNBOUND', 'UNBOUND_ERROR', 'UNBOUND_REMOVE',

Some files were not shown because too many files have changed in this diff Show More