633 Commits

Author SHA1 Message Date
Bas Schoenmaeckers
2c46b69e4b Add weakref support to c-api (#8006) 2026-06-02 15:31:21 +09:00
Bas Schoenmaeckers
885cf5c29c Add set object functions to c-api (#8002) 2026-06-02 00:35:43 +09:00
Shahar Naveh
8018ba689f Add errno.EHWPOISON (#7996) 2026-05-31 21:31:21 +09:00
Shahar Naveh
26f817a55b Fix broken CI (rustc 1.96) (#7992)
* Fix lints for clippy 1.96

* Try to fix example

* Put fix in correct place
2026-05-30 20:16:49 +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
Bas Schoenmaeckers
dcb273ba68 Add capsule support to c-api (#7940) 2026-05-26 23:12:54 +09:00
Shahar Naveh
9701c46d86 Clippy rules (test related) (#7968) 2026-05-25 22:03:18 +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
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
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
Jeong, YunWon
d3272e752b Align codegen metadata with CPython (#7952) 2026-05-23 20:16:03 +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
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
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
Shahar Naveh
0063a6d18b Align more error messages with CPython 3.14.5 (#7928) 2026-05-20 08:59:59 +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
Shahar Naveh
e8d7437d91 Update test_types.py to 3.14.5 (#7912) 2026-05-19 08:21:19 +00: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
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
Shahar Naveh
54589bf255 Remove some #[allow(clippy::*)] (#7878)
* Remove some `#[allow(clippy::*)]`

* Fix after merge
2026-05-17 01:56:47 +09:00
Jeong, YunWon
8253253fc7 more hostenv impl (#7604) 2026-05-16 19:01:40 +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
Shahar Naveh
460b1d39ed Clippy warn uninlined_format_args & redundant_else (#7873) 2026-05-15 16:52:06 +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
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
9d77b25858 Add more pedantic clippy rules (#7830) 2026-05-12 20:57:37 +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
4a46e84eb9 Add map_unwrap_or clippy rule (#7829) 2026-05-12 00:51:23 +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
Joshua Megnauth
4aa73ddab2 Use rustix for uname; drop uname crate (#7799) 2026-05-10 00:26:26 +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
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
Noa
02932384d6 Use cfg_select in a bunch more places (#7740) 2026-05-04 20:26:16 +00: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