17504 Commits
Author SHA1 Message Date
Jeong, YunWon e56dfa186d Address #8807 review: negative defaults, to_bytes signed, get_self, fstat (#8810)
* address review: negative defaults, to_bytes signed, get_self, fstat

- python_default_repr renders negated int/float literals and looks
  through parentheses and groups.
- int.to_bytes signed is ArgIntoBool again, so any object is accepted
  by truthiness; its signature default stays False via py_default.
- PyNativeFunction::get_self falls back to the module object.
- os.fstat has its own FromArgs with a single fd parameter instead of
  reusing stat's arguments.

Assisted-by: Claude:claude-opus-5-5

* address review: literal default rendering and fstat supports sets

- Literal defaults are rendered by python_literal_repr, which recurses
  through negation, parentheses and groups, so `-(1)` renders `-1`.
- Float literals without a dot or exponent, such as `1f64`, render
  with a trailing `.0`.
- os.fstat is removed from supports_fd, supports_dir_fd and
  supports_follow_symlinks.

Assisted-by: Claude:claude-opus-5-5
2026-09-25 00:53:31 +09:00
Bas SchoenmaeckersandJeong, YunWon a1971c1d9d Unify PyObject dereferencing in c-api (#8292)
* Add `FfiPtrExt` for uniform pointer dereferences

* Panic on NULL ptrs

* capi: import type_name from core, not std

Assisted-by: Grok:4.6

* Use `assume_owned_or_err` in PyGetSetDef

* Update latest code

---------

Co-authored-by: Jeong, YunWon <jeong@youknowone.org>
2026-09-25 00:48:15 +09:00
Bas Schoenmaeckers 3272bee78b Add PyNumber_AsSsize_t to the c-api (#8801) 2026-09-24 22:49:20 +09:00
Jeong, YunWon 2619bdcca5 Fix hasattr (#8809) 2026-09-24 22:48:15 +09:00
Jeong, YunWon 155cea292f Store instance slots as atomic object pointers (#8804)
* Store instance slots as atomic object pointers

PyMemberDef now carries type, offset, and flags. Slot values are
atomic PyObject pointers with try-incref reads, so a concurrent
store cannot free the memory under a reader. Objects placed in a
slot stay published until QSBR reclaims them.

Assisted-by: Grok:4.7

* Collapse nested slot load condition

Assisted-by: Grok:4.7

* Reject over-long digit strings before parsing

A pure ASCII digit string longer than the int digit limit now fails
from a wide digit check. The denial-of-service test no longer spends
the failure path scanning and parsing the whole literal.

Assisted-by: Grok:4.7

* Collapse nested digit-limit condition

Assisted-by: Grok:4.7

* Restore MemberKind for member type codes

The VM stores the member type as an enum whose discriminants match
the C type codes. The C API still reads the integer and converts it
at the boundary. Flags stay integers.

Assisted-by: Grok:4.7

* Mark MemberKind::from_i32 as must_use

Assisted-by: Grok:4.7
2026-09-24 21:40:49 +09:00
Jeong, YunWon 7cd4a54e1c Compose native text signatures at compile time from FromArgs (#8807)
* Compose native text signatures at compile time from FromArgs

FromArgs gains a PARAMS const describing its Python parameters.
The derive builds the whole internal doc, `name(sig)\n--\n\ndoc`,
in a const context from those params, and `__text_signature__` /
`__doc__` parse it from the doc. The macro-time string path and the
runtime sig_parts composition are removed.

Signatures follow clinic conventions: `$module` for module functions,
Rust literal defaults rendered as Python reprs, other defaults as
`<unrepresentable>`, and a new `#[pyarg(py_default = "...")]` for the
few fields whose Rust default differs from the Python one. The
`#[pyarg]` keys are documented on the FromArgs derive.

Module functions keep their module object and return it from
`__self__`. PyNativeFunction now traverses its references.

os.stat, os.mkdir, os.listdir, os.open, dict.fromkeys, round, sorted,
int.to_bytes and bytes.hex take arguments matching their signatures.

Assisted-by: Grok:grok-4.7
Assisted-by: Claude:claude-opus-5-5

* Update builtin_signature snippet and fix mkdir on Windows

round and sum now report their FromArgs parameters, so the snippet
asserts their signatures instead of their absence. sum's start is a
PyObjectRef defaulting to int 0 with py_default "0". mkdir reads
dir_fd only on platforms that use it.

Assisted-by: Claude:claude-opus-5-5

* Remove expectedFailure from two signature tests

test_cmp_to_signature and test_autospec_on_bound_builtin_function
now pass. The TestCmpToKeyC override existed only for the marker.

Assisted-by: Claude:claude-opus-5-5
2026-09-24 20:53:28 +09:00
Jeong, YunWon e540b4152d Borrow exception and dict methods that only read self (#8808)
add_note, __reduce__, and __setstate__ take &Py<BaseException>.
dict.__ror__ takes &Py<PyDict>. Callers drop the clone made for add_note.

Assisted-by: Grok CLI:grok-4.7
2026-09-24 20:46:06 +09:00
Jeong, YunWon 4174a77cac Pass owned iterables through; host enumerate on Py<T> (#8806)
* Pass owned iterables through; host enumerate on Py<T>

dict update, OrderedDict, defaultdict, itertools, and pickle pass an
owned object to PyIter::try_from_object.

#[pyclass(...)] impl Py<T> now emits PyClassImpl for T. enumerate's
methods live on that impl. A bare impl Py<T> stays an extension.

Assisted-by: Grok CLI:grok-4.7

* Host Self-free pyclass impls on Py<T>

filter, Ellipsis, NotImplemented, bool, Placeholder, and NoDefault
do not use Self as the payload type, so their pyclass impl can be Py<T>.

Assisted-by: Grok CLI:grok-4.7
2026-09-24 18:41:41 +09:00
Jeong, YunWon 9d8709e79c Pass owned iterables straight into PyIter (#8805)
GET_ITER, GET_YIELD_FROM_ITER, mapping keys(), and builtin iter()
already hold a PyObjectRef. get_iter() cloned it before try_from_object.

Assisted-by: Grok CLI:grok-4.7
2026-09-24 15:32:40 +09:00
Jeong, YunWon 0940849029 Borrow tp_descr_get/tp_init arguments and by-value public helpers (#8803)
* Borrow tp_descr_get/tp_init arguments and by-value public helpers

- DescrGetFunc and GetDescriptor::descr_get take
  (&PyObject, Option<&PyObject>, Option<&PyObject>); impls call
  .to_owned() where they store or return an argument.
- InitFunc takes &PyObject and Initializer::init takes &Py<Self>.
- VirtualMachine::get_attribute_opt, print_exception,
  call_get_descriptor(_specific) and other helpers flagged by
  clippy::needless_pass_by_value take &PyObject / &Py<T>; callers in
  crates/, examples/ and tests/ drop the clones made for the call.

Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Grok CLI:grok-4.7

* Pass the borrowed zelf to internal_close in WindowsConsoleIO init

Assisted-by: Claude Code:claude-opus-5-5

* Fix borrowed signatures missed by the rebase

OrderedDict.init, classmethod_descriptor.descr_get, and the warnings
get_attribute_opt callers still used owned handles.

Assisted-by: Grok CLI:grok-4.7
2026-09-24 14:25:07 +09:00
Jeong, YunWonandLee Dogeon afad92bf1b Add OrderedDict, marshal, inspect, and chflags bitflagset (#8780)
* Implement _collections.OrderedDict

Extracted from #6799: native OrderedDict as a dict subclass,
iterator mutation tracking, and mapping-view set operations.

* Add OrderedDict, warnings source lines, and socket sendmsg

Implement _collections.OrderedDict as a dict subclass with a
linked list of insertion-order nodes.

Pack struct F/D as native float/double complex pairs, and report
unexpected __replace__ field names as a list repr.

Register encodings.latin_1 at startup. Reject NUL in input()
prompts. Copy non-exact type dicts in mapping order.

Fetch warning source lines through _bless_my_loader and unicode
splitlines. Keep the last warnings.filters list after deletion.

Snapshot sendmsg sequences before converting buffers, parse
ancillary ints with __index__, and dispatch AF_CAN by protocol.

Store sys.trace and sys.profile on each ThreadSlot so
_settraceallthreads/_setprofileallthreads are visible to
sys.gettrace/getprofile on other threads.

Use <frozen importlib._bootstrap> as frozen importlib
co_filename. Keep HEAPTYPE socket objects GC-tracked.

Assisted-by: Grok:grok-4.6

* Load marshal via readinto and dump sets in dumps order

marshal.load fills a scratch buffer with readinto on a
contiguous writable memoryview, and rejects a returned length
other than the requested size.

set and frozenset dumps write elements in sorted(v,
key=marshal.dumps) order.

Assisted-by: Grok:grok-4.6

* Add classmethod_descriptor and ABC IS_ABSTRACT flags

Give METH_CLASS methods a distinct classmethod_descriptor type.
Store Py_TPFLAGS_IS_ABSTRACT from __abstractmethods__ and look
up that attribute only in the type's own dict.

Expose defaultdict.default_factory as a member descriptor.
Inherit bool's unused number slots from int. Mark set and
frozenset __contains__ as METH_COEXIST.

Parse classmethod, staticmethod, object.__new__, and heap type
text signatures. Register interactive source in linecache.

Assisted-by: Grok:grok-4.6

* address review: odict reuse, descr call, new wrapper

Reuse odict node slots and walk the live list in next_after.
Require both | operands to be dicts. Drop the odict iterator
on GC clear. Bind classmethod descriptors through descr_get
and update IS_ABSTRACT under the type lock. Give odict keys
and items sq_contains. Walk tp_new_wrapper past new_wrapper
so HEAPTYPE socket subclasses can call socket.__new__.
Split FreeBSD Bluetooth protocol constants from Linux values.

Assisted-by: Grok:grok-4.6

* Represent chflags bits with bitflagset

Assisted-by: Grok:grok-4.6

* Represent access and mode bits with bitflagset

Assisted-by: Grok:grok-4.6

* Continue unfinished REPL suites as incomplete input

Assisted-by: Grok:grok-4.6

* Add pickle and os.stat inspect signatures

Give _pickle.Pickler the clinic constructor text signature and
accept fix_imports and buffer_callback as positional-or-keyword.
Allow #[pyfunction(text_signature)] and use it so os.stat reports
(path, *, dir_fd=None, follow_symlinks=True). Uncomment import
_pickle and drop the pickle inspect expectedFailures.

Assisted-by: Grok:grok-4.6

* Add Unpickler, dump/load, and lstat signatures

Give _pickle Unpickler, dump, dumps, load, and loads clinic
text signatures, and os.lstat (path, *, dir_fd=None).

Assisted-by: Grok:grok-4.6

* Complete OrderedDict with insertion-order links

Move the native OrderedDict into ordered_dict.rs and track order
with a linked list of nodes, keeping mapping-view set operations.

Assisted-by: Grok:grok-4.6

* address CI: stat casts, HCI_DATA_DIR, passing xfails

Compare stat permission bits as Mode so Linux clippy does not
flag a u32-to-u32 cast. Export HCI_DATA_DIR with the Bluetooth
socket option values. Drop expectedFailure from tests that now
pass.

Assisted-by: Grok:grok-4.6

* Fix wasm teardown deadlock and bluetooth constants

Assisted-by: Grok:grok-4.6

* Clear process-wide eval breaker bits in STW test

Assisted-by: Grok:grok-4.6

* Drop exclusive-held panic that tripped wasm clippy

Assisted-by: Grok:grok-4.6

* Fix OrderedDict contains and export Windows BDADDR

Assisted-by: Grok:grok-4.6

* Add cmp_to_key and hash-once LRU cache

_functools.cmp_to_key now returns KeyWrapper. LRU lookups store the
key hash on _lru_list_elem so __hash__ is not called again on hits
or eviction.

Assisted-by: Grok:grok-4.6

* Allow subclass keywords on list, tuple, and float

list.__init__ rejects keywords only when tp_new is still list's.
tuple/float.__new__ reject them only when tp_init is still the base's.

Assisted-by: Grok:grok-4.6

* Fix CI lint, test re-export, and structseq members

Drop the duplicate clear_eval_breaker_for_test re-export.
Format structseq imports. Expose struct sequence fields as
readonly members so assignment raises "readonly attribute".

Assisted-by: Grok:grok-4.6

* Allow subclass keywords on itertools and filter

Positional-only constructors ignore extra keywords when a subclass
has replaced tp_init, matching list/tuple/float.

Assisted-by: Grok:grok-4.6

* Remove text_signature derive parameter

Drop the string override on #[pyfunction] and #[pyclass]. Signatures
are generated from the Rust parameter list only. Restore inspect
expectedFailures that depended on the override.

Assisted-by: Grok:grok-4.6

* Rename set contains_method to __contains__

Keep a borrowed contains helper for slots and C-API. The coexist
pymethod is named __contains__.

Assisted-by: Grok:grok-4.6

* Use MonitoringEvent instead of EVENT_* aliases

Drop the unused MonitoringEvents bitflags and the EVENT_PY_START
style mask constants. Call sites use the enum and .mask().

Assisted-by: Grok:grok-4.6

* Store warnings filters as PyListRef

The list already serializes its elements. Drop the extra mutex around
the cached list pointer.

Assisted-by: Grok:grok-4.6

* Re-export clear_eval_breaker_for_test after rebase

Keep the STW test helper visible without bringing back stop_bit_set.

Assisted-by: Grok:grok-4.6

* Fix groupby reentrancy and islice arguments

Give _grouper its own tgtkey and take currvalue after the key
comparison. Accept __index__ on islice bounds, reject step 0, and
drain the source up to stop.

Assisted-by: Grok:grok-4.6

* SETREF warnings filters, onceregistry, and defaultaction

Keep the last list, dict, or str after the module attributes are
deleted by swapping the interpreter cache pointer. Do not store a
None fallback for _warnings_context.

Assisted-by: Grok:grok-4.6

* Iterate OrderedDict inner dict via next_entry

&PyDict is not an iterator after the borrowed-ref signature change.

Assisted-by: Grok:grok-4.6

* Remove expectedFailure from testBluetoothConstants

Linux now exports BTPROTO_HCI and the rest of the set.

Assisted-by: Grok:grok-4.6

* Borrow get_filter arguments as PyObject

Match the warn_explicit call site that passes borrowed
handles after the internal-helper borrow change.

Assisted-by: Grok:grok-4.6

* Fix bool slots and SemHandle safety docs

Leave unused bool number slots unset so inspect reports
__add__ on int. Document SemHandle::from_raw safety.

Assisted-by: Grok:grok-4.6

* Keep sendmsg errors and drop duplicate linecache

Replace sendmsg iterable TypeError only. Register REPL
source in linecache once per compiled input.

Assisted-by: Grok:grok-4.6

* Nest REPL parse or-patterns for clippy

Assisted-by: Grok:grok-4.6

* Mark pydoc os.stat signature test expectedFailure

Assisted-by: Grok:grok-4.6

---------

Co-authored-by: Lee Dogeon <dev.moreal@gmail.com>
2026-09-24 12:21:26 +09:00
Joshua Megnauth 86c7fa20c4 Fix missing workspace lints & fix lints (#8781)
Some of the workspace packages were missing the `[lints]` table. I
enabled the lints then fixed the resulting errors.
2026-09-24 07:49:50 +09:00
Jeong, YunWon 30ff0329a2 Borrow handles in internal helpers that only borrowed them (#8798)
Internal functions that took PyObjectRef or PyRef<T> by value but only
borrowed it (reported by clippy::needless_pass_by_value) now take
&PyObject or &Py<T>. Callers pass a borrow instead of an owned handle;
61 caller-side .clone()/.to_owned() calls made only to pass the argument
are removed. Where a converted body already cloned the parameter, that
.clone() became .to_owned().

Functions with #[py*] attributes, trait methods, and functions whose
signature is fixed by a function pointer, getset or slot wrapper type are
unchanged.

PyFloat_FromString in crates/capi passes the borrowed object to
parse_float_from_string without taking an owned reference.

Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Grok CLI:grok-4.7
2026-09-23 23:53:03 +09:00
Jeong, YunWon 77b3a303a1 host_env: rebuild a unix SemHandle from its stored integer (#8797)
* host_env: rebuild a unix SemHandle from its stored integer

Windows already has SemHandle::from_raw. Unix callers that keep the
semaphore integer and close it themselves need the same constructor.
Drop still closes, so that caller forgets the rebuilt handle.

Assisted-by: Grok

* Update crates/host_env/src/multiprocessing.rs
2026-09-23 22:54:05 +09:00
Bas Schoenmaeckers 15c9bdd48f Impl PyBytes_FromString PyBytes_FromObject PyBytes_AsStringAndSize (#8795)
* Impl `PyBytes_FromString` `PyBytes_FromObject` `PyBytes_AsStringAndSize`

* Do not copy in PyBytes_FromObject when already a bytes obj
2026-09-23 21:03:36 +09:00
Bas Schoenmaeckers 43a595ed65 Add SeqIter and CallIter support to c-api (#8794) 2026-09-23 19:31:31 +09:00
Jeong, YunWon 05f1386fca host_env: publish remaining Windows socket constants (#8792)
* host_env: publish remaining Windows socket constants

Windows SOMAXCONN follows winsock2.h (0x7fffffff), not the Winsock 1.1
value windows-sys still exports. The socket module takes the signed
INADDR group constants and the other Winsock names host_env already
defines.

Assisted-by: Grok

* test_socket: expect Bluetooth constants to pass on Windows

The Windows names this test reads are published now, so the blanket
expectedFailure is an unexpected success there. Other platforms still
miss BTPROTO_HCI and the rest of the non-Windows set.

Assisted-by: Grok
2026-09-23 18:28:08 +09:00
Jeong, YunWon 56df93a919 Replace borrowed &PyObjectRef and &PyRef<T> with &PyObject and &Py<T> (#8793)
Parameters, return types, closure annotations, Fn and Iterator bounds,
trait methods and struct fields that borrowed an owning handle now borrow
the object: &PyObjectRef -> &PyObject, &PyRef<T> / &PyXxxRef -> &Py<T>.
Existing .clone() calls on those values became .to_owned().

- Move PyTuple::extract_tuple into impl Py<PyTuple>; FromPyTuple::from_pytuple
  takes &Py<PyTuple>.
- DictIter and IntoIterator for &PyDict use &Py<PyDict>; apply_shared_ns
  takes &Py<PyDict>.
- PyExactSizeIterator, calculate_suggestions, collection_repr and the cformat
  tuple helpers take iterators over &PyObject; callers map with |o| &**o.
- Return types changed: TypeNamespace::as_dict, Context::cached_int,
  struct_error_type, PyBoundMethod::function_obj/self_obj,
  AsRef for ArgIndex. as_dict, function_obj and self_obj are no longer
  const fn.
- asyncgenerator unbox, csv parse_lineterminator and _ctypes bytes
  parameter take &Py<T> instead of &T.

Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Grok CLI:grok-4.7
2026-09-23 18:27:33 +09:00
Jeong, YunWon 3ac2afd044 Start socket timeout after the sock snapshot (#8791) 2026-09-23 16:59:56 +09:00
Jeong, YunWon 8e4f6d2ab8 host_env: export SIG*, sysconf, CLOCK_*, unix socket names (#8790)
* host_env: export SIG*, sysconf, CLOCK_*, unix socket names

Re-export signal numbers, pathconf/sysconf names, clock ids,
and the unix socket integers the VM still took from libc.

Assisted-by: Claude

* stdlib: take SIG*, sysconf, CLOCK_*, socket names from host_env

signal, posix pathconf/sysconf enums, time clock ids, and the
shared socket pyattrs re-export host_env instead of libc.

Assisted-by: Claude

* host_env: export remaining socket integers and Windows SHUT_*

Alias WinSock SD_* as SHUT_* so the shared pyattr names resolve.
Re-export the rest of the libc socket integers the VM still took
from `use libc as c`.

Assisted-by: Claude

* stdlib: take remaining socket pyattrs from host_env

Module integer names come from host_env. Types and syscalls stay
on libc / WinSock.

Assisted-by: Claude

* stdlib: drop unused Windows socket constant imports

pyattrs now take those names from host_env. Keep only the syscall
names the shared body still reads as c::.

Assisted-by: Claude
2026-09-23 16:59:36 +09:00
Jeong, YunWon e4984d595d Drop racy STOP_BIT assertion from eval-breaker test (#8789)
The isolated test asserted a clear process-wide eval breaker, and
cleared STOP_BIT itself. Parallel STW tests flicker that word, so
Windows rust tests failed after #8786.

Assisted-by: Grok:4.6
2026-09-23 12:52:40 +09:00
Jeong, YunWon cb3d5dbb1a Take &Py<T> in getset closures, try_value_with, slice and ssl helpers (#8787)
- `try_value_with` passes `&Py<T>` to its closure instead of `&T`, and
  its four callers follow.
- structseq and pyexpat getset closures take `&Py<T>`.
- `get_jit_args` takes `&Py<PyFunction>`.
- `SaturatedSlice::with_slice` takes `&Py<PySlice>`; `to_saturated`
  moves from `impl PySlice` to `impl Py<PySlice>`.
- The ssl compat helpers take `&Py<PySSLSocket>`; `do_handshake`,
  `read`, `write` and `shutdown` take `zelf: &Py<Self>` so they can
  pass it on. An inherent `impl Py<PySSLSocket>` is not possible in
  the stdlib crate.

The csv `&PyDialect` sites keep the payload reference: the dialect is
stored by value, so no `Py<PyDialect>` is available there.

Assisted-by: Claude Opus 5 (1M context)
Assisted-by: grok-4.7
2026-09-23 08:13:21 +09:00
Jeong, YunWon 01bd5ca370 host_env: export O_* flags, wasm errno, and posix names (#8788)
* host_env: export O_* flags and wasm errno

Re-export open(2) flags from os. wasm32-unknown-unknown uses
Darwin/BSD numbers, matching the guest errno table.

Expand errno_wasm to that table plus strerror.

Assisted-by: Claude

* stdlib: take O_* open flags from host_env

os, posix, and nt re-export the host_env names instead of libc.

Assisted-by: Claude

* host_env: export remaining posix integer names

Publish wait, RTLD, SCHED, SEEK, PRIO, CLD, AT, lockf, and
statvfs flags so posix and os stop taking them from libc.

Assisted-by: Claude

* stdlib: take remaining posix names from host_env

posix and os re-export wait, RTLD, SCHED, SEEK, PRIO, and
statvfs flags from host_env.

Assisted-by: Claude

* host_env: define AT_EACCESS on Android

bionic's libc crate does not export AT_EACCESS. Use the Linux
fcntl value there.

The eval-breaker unit test now waits for STOP_BIT to be clear
instead of requiring the whole process-wide breaker word empty.

Assisted-by: Claude

* vm: gate stop_bit_set to tests

Clippy builds the lib without tests, so the helper and its
re-export were unused.

Assisted-by: Claude
2026-09-23 08:12:02 +09:00
Jeong, YunWon 2b8c095e11 address review: keep sendall deadline; isolate STW test (#8786)
Pass sendall's absolute Deadline through sock_op_timeout_err.
Add a test that trips eval_breaker_tripped with STOP_BIT clear.

Assisted-by: Grok:4.6
2026-09-22 23:04:45 +09:00
dependabot[bot] ea4e51d52d build(deps): bump https://github.com/astral-sh/ruff-pre-commit (#8777)
Bumps [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit) from v0.16.4 to 0.16.7.
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.16.4...v0.16.7)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.16.7
  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-09-22 14:55:37 +09:00
Jeong, YunWon 979ea4c4dc host_env: export vsock, wasm socket, Darwin flags (#8779)
* host_env: export vsock, wasm socket, Darwin flags

Publish vsock/UDPLITE/SOL fallbacks, wasm socket and errno
constants, remaining BSD chflags, and S_IFDOOR/PORT/WHT.

Assisted-by: Claude

* stdlib: take vsock, wasm socket, Darwin flags from host_env

_socket, wasm _socket/_io/errno, _stat, and openssl alerts now
re-export the host_env names.

Assisted-by: Claude

* host_env: keep wasm socket/errno off emscripten

wasm32-unknown-emscripten is unix, so the stub modules must be
unknown-unknown (and WASI) only.

Assisted-by: Claude

* stdlib: match socket module_def cfg to the module

Assisted-by: Claude

* host_env: drop duplicate Windows SOL_UDP

The IANA fallback also compiled on Windows, colliding with the
WinSock definition.

Assisted-by: Claude
2026-09-22 14:55:16 +09:00
Jeong, YunWon eb1546ae08 posix: add os.forkpty so pty.spawn does not hang (#8782)
* posix: add os.forkpty

pty.fork prefers forkpty so a nested spawn is a new session with its
own controlling tty. The openpty+login_tty fallback hung the
jsontests collector.

Assisted-by: Claude

* select: rebuild fd sets after EINTR

select(2) updates the sets in place. Retrying an interrupted call
with empty sets and a NULL timeout blocks forever.

Assisted-by: Claude

* http.server: keep CGI off the body-read timeout

A millisecond handler timeout races the client thread writing the
request and is inherited across CGI fork, which RSTs the client
under load.

Assisted-by: Claude

* Revert "http.server: keep CGI off the body-read timeout"

Lib edits outside a CPython copy are not allowed.

Assisted-by: Claude

* host_env: note why forkpty is libc not nix

Assisted-by: Claude

* benches: run deepcopy 10x more iterations

Assisted-by: Claude
2026-09-22 14:54:46 +09:00
Jeong, YunWon f56bfc9a5c Show stdin traceback lines; omit StopIteration None args (#8778)
* Show stdin traceback lines; omit StopIteration None args

Register interactive <stdin> source in linecache so REPL
tracebacks include the source line and caret. Build
StopIteration with empty args when the value is None, and
wrap monitoring STOP_ITERATION the same way.

Unskip REPL stderr flush tests and pdb Flaky doctests.

Assisted-by: Grok:4.6

* Take &Py<PyCode> in register_code_in_linecache

Assisted-by: Grok:4.6

* Count Unknown closing braces in tokenize EOF check

`$` inside an f-string interpolation is TokenKind::Unknown, so
the matching `}` is Unknown rather than Rbrace and generate_tokens
raised TokenError. Also convert &Py<PyCode> to PyObjectRef
explicitly when registering linecache.

Assisted-by: Grok:4.6

* Unskip test_pdb.PdbTestCase.test_zipimport

The test now passes, so the expectedFailure is an unexpected
success under CI.

Assisted-by: Grok:4.6

* Park STW threads; poll sockets after timeout deadline

Check per-thread stop_requested in eval_breaker_tripped so
workers still park when another interpreter clears STOP_BIT.

Start socket timeout after the fd snapshot, and poll once with
timeout 0 when the deadline has already passed, so a readable
socket is not dropped as timed out.

Assisted-by: Grok:4.6
2026-09-22 14:53:52 +09:00
Jeong, YunWon 23fcb8d913 Format the README JIT example for ruff-format (#8785)
ruff-format 0.16.7 formats Python fences in Markdown and wants two
blank lines after a top-level function.

Assisted-by: Grok:4.6
2026-09-22 10:49:05 +09:00
dependabot[bot] e3fb1eaa8a build(deps): bump reviewdog/action-actionlint from 1.73.4 to 1.74.0 (#8774)
Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.73.4 to 1.74.0.
- [Release notes](https://github.com/reviewdog/action-actionlint/releases)
- [Commits](https://github.com/reviewdog/action-actionlint/compare/d290e336d5a743810aef4404f757dc862276d2ae...8b682e1e7e512151d73a6b9449cbff173846226e)

---
updated-dependencies:
- dependency-name: reviewdog/action-actionlint
  dependency-version: 1.74.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-09-22 10:14:13 +09:00
dependabot[bot] 854b5af434 build(deps): bump jiff in the jiff group across 1 directory (#8768)
Bumps the jiff group with 1 update in the / directory: [jiff](https://github.com/BurntSushi/jiff).


Updates `jiff` from 0.2.35 to 0.2.37
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/jiff-static-0.2.37/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.35...jiff-static-0.2.37)

---
updated-dependencies:
- dependency-name: jiff
  dependency-version: 0.2.37
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: jiff
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-22 09:49:54 +09:00
dependabot[bot] 28ba022056 build(deps): bump crossbeam-utils from 0.8.22 to 0.8.23 (#8770)
Bumps [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) from 0.8.22 to 0.8.23.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/main/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-utils-0.8.22...crossbeam-utils-0.8.23)

---
updated-dependencies:
- dependency-name: crossbeam-utils
  dependency-version: 0.8.23
  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-09-22 09:49:45 +09:00
dependabot[bot] 94298caaf2 build(deps): bump zlib-rs from 0.6.7 to 0.6.8 (#8772)
Bumps [zlib-rs](https://github.com/trifectatechfoundation/zlib-rs) from 0.6.7 to 0.6.8.
- [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.6.7...v0.6.8)

---
updated-dependencies:
- dependency-name: zlib-rs
  dependency-version: 0.6.8
  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-09-22 09:26:04 +09:00
dependabot[bot] 0c5788150d build(deps): bump reviewdog/action-suggester from 1.24.3 to 1.25.0 (#8771)
Bumps [reviewdog/action-suggester](https://github.com/reviewdog/action-suggester) from 1.24.3 to 1.25.0.
- [Release notes](https://github.com/reviewdog/action-suggester/releases)
- [Commits](https://github.com/reviewdog/action-suggester/compare/2558ba17e65a9039e73764a73009fc05fef28a46...4553543bea739d9d503379d786148cfd561de5fa)

---
updated-dependencies:
- dependency-name: reviewdog/action-suggester
  dependency-version: 1.25.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-09-22 09:25:50 +09:00
dependabot[bot] 2116a6d38a build(deps): bump cranelift in the wasmtime group across 1 directory (#8769)
Bumps the wasmtime group with 1 update in the / directory: [cranelift](https://github.com/bytecodealliance/wasmtime).


Updates `cranelift` from 0.135.1 to 0.135.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.135.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-09-22 09:25:41 +09:00
dependabot[bot] 81bd48c26c build(deps): bump zizmorcore/zizmor-action from 0.6.3 to 0.6.4 (#8773)
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.6.3 to 0.6.4.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/70fb788f84895a7701f5643d103d587e460b5c99...cc914d7f3750a2d13d75c7f184a1060aa0e9d482)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.6.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-09-22 01:16:53 +09:00
dependabot[bot] 16998af53d build(deps): bump github/gh-aw/actions/setup from 0.88.2 to 0.89.14 (#8776)
Bumps [github/gh-aw/actions/setup](https://github.com/github/gh-aw) from 0.88.2 to 0.89.14.
- [Release notes](https://github.com/github/gh-aw/releases)
- [Changelog](https://github.com/github/gh-aw/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/gh-aw/compare/8e30bcd8897f5047051fa3971188e1dd4cdb23cf...97b9a7c376bb60bd7cbcf348a5a6349b52e266ff)

---
updated-dependencies:
- dependency-name: github/gh-aw/actions/setup
  dependency-version: 0.89.14
  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-09-22 00:18:25 +09:00
Jeong, YunWon fe45a7a35f host_env: export ctypes, chflags, SIG_DFL (#8766)
* host_env: export ctypes, chflags, SIG_DFL

Publish ctypes FUNCFLAG/TYPEFLAG/DICTFLAG and CTYPES_MAX_ARGCOUNT,
BSD chflags bits libc does not bind, AT_FDCWD, and SIG_DFL/IGN/ERR.

Assisted-by: Claude

* stdlib: take ctypes, chflags, SIG_DFL from host_env

_ctypes, _stat, os.AT_FDCWD, and Windows SIG_DFL/IGN/ERR now re-export
the host_env names. Wasm keeps local SIG_* literals.

Assisted-by: Claude
2026-09-21 22:20:10 +09:00
Jeong, YunWon 8fda0eccee Fix fork-child SIGSEGV and yield-from throw() args (#8759)
* Unskip passing flaky CPython tests

Remove RUSTPYTHON skip overlays after Linux release repeats passed
for enum, threading, logging, subprocess, socket, int, re, strtod,
and regrtest cases.

Assisted-by: Grok:4.6

* Fix fork-child SIGSEGV and yield-from throw args

Forget JoinHandle of threads that did not survive fork instead
of dropping them. Zero-reinit the import lock and condvars in
the child rather than unlock/notify. Resume the world if
os.fork() fails.

Call a yield-from subiterator throw() without None fillers so
a 1-arg throw stays 1-arg.

Drop the stale Windows expectedFailure on logging queue config
and unskip test_reinit_tls_after_fork plus the asyncio
local_addr family test.

Assisted-by: Grok:4.6

* Restore import lock depth after fork; add WASI clock info

Zeroing IMP_LOCK in the child discarded its recursion count, so
imp.release_lock() raised and test_fork1 exited 1. Re-acquire the
saved depth, then drop the extra pre-fork hold.

Expose time.get_clock_info on WASI via clock_getres so test_int
Stopwatch can run.

Unskip lock_tests test_acquire_contended and test_thread_leak.

Assisted-by: Grok:4.6

* Honor chmod follow_symlinks=False via fchmodat

Linux chmod followed the symlink, so dangling links raised
FileNotFoundError instead of NotImplementedError. Use fchmodat
with AT_SYMLINK_NOFOLLOW and map ENOTSUP to NotImplementedError.

Unskip httpservers truncated Content-Length (CPython already
skips as root) and tempfile symlink-mode cleanup on Linux.

Assisted-by: Grok:4.6

* Unskip httplib local bad-hostname HTTPS test

The skip was leftover. With default ssl-rustls the test passes
under -W error on Linux.

Assisted-by: Grok:4.6

* Fix list cmp deadlock, wasm digit overflow, REPL prompt

Do not hold list/deque locks across item comparison callbacks.
Compute int-to-str digit bounds in u64 so the check cannot wrap
on wasm32. Snapshot set contents when list.extend copies a set.

For a non-TTY stdin, write the REPL prompt to stderr and read
lines from stdin. Flush stdout/stderr after each interactive
command. Set sys.ps1/ps2 to ">>> " and "... ".

Unskip list mutation compares, asyncio all_tasks race, importlib
deadlock/parallel/circular-import tests, and REPL stdout flush.

Assisted-by: Grok:4.6

* Avoid unnecessary u64 cast in int-to-str digit check

bits() is already u64, so `bits as u64` fails clippy
unnecessary_cast under -Dwarnings.

Assisted-by: Grok:4.6
2026-09-21 20:53:53 +09:00
Jeong, YunWon a46627a33c Give trampoline frames a unique non-Copy handle (#8762)
run_trampoline takes &mut again; the loop holds TrampolineIFrame so
two &mut InterpreterFrame cannot alias the same allocation.

Assisted-by: Grok:4.6
2026-09-21 20:48:45 +09:00
Jeong, YunWon 803520930a capi: add PyFrame_GetLocals, GetGlobals, GetBuiltins (#8761)
Wire the C-API getters to frame.f_locals, f_globals, and f_builtins,
and cover write-through vs snapshot locals in a capi test.

Assisted-by: Grok:4.6
2026-09-21 19:38:05 +09:00
Jeong, YunWon 7b018871a3 host_env: export remaining stdlib constants (#8760)
* host_env: export winsound, spawn, and remaining SSL_OP flags

Assisted-by: Claude

* stdlib: take winsound, nt spawn, and SSL_OP names from host_env

Assisted-by: Claude

* host_env: export ssl alerts, sysexits, socket extras

Assisted-by: Claude

* stdlib: take remaining host_env and common constants

Assisted-by: Claude

* Remove unused workspace exitcode dependency

Assisted-by: Claude

* host_env: export PROTO, access, reparse, wait flags

Assisted-by: Claude

* stdlib: take PROTO, access, reparse, wait from host_env

Assisted-by: Claude
2026-09-21 19:37:16 +09:00
Jeong, YunWon 53a906c916 Match settrace events, f_lineno jumps, and type dict lookup (#8758)
* Keep type dict keys and report current f_lasti

Snapshot the MRO during lookup so non-string keys can run __eq__,
skip subclass updates when set_bases is reentered, make module
__dict__ a readonly member, and forbid deleting exception __dict__.
f_lasti is the current opcode; line events skip NO_LOCATION units.

Assisted-by: Grok:grok-4.6

* Fire settrace call events from RESUME

PY_START/PY_RESUME become a 'call' event only at the first RESUME
of a frame activation, so lasti is the resume unit and f_trace_lines
is honored. Unskip passing TraceTestCase methods.

Assisted-by: Grok:grok-4.6

* Record what_event so f_lineno jumps match line traces

Store the current monitoring event while a trace callback
runs, reject jumps from call/return/exception, unwind tracer
errors in the traced frame, and fire call on PY_THROW.
Unskip JumpTestCase except the infinite-loop and five remaining xfails.

Assisted-by: Grok:grok-4.6

* Fire remaining settrace events from opcodes and jumps

Keep GET_ANEXT's iterator in mark_stacks, dispatch non-call
events through f_trace, fire PY_YIELD/PY_RETURN from the
opcodes, emit same-line LINE on backward jumps, and raise at
flattened call sites. Unskip the rest of test_sys_settrace.

Assisted-by: Grok:grok-4.6

* Fix trampoline raise panic and unxfail passing traces

Do not decode inline-cache units as opcodes when a flattened
call raises. Remove expectedFailure from test_trace and
test_regrtest coverage tests that now pass.

Assisted-by: Grok:grok-4.6

* Fire profile returns without a per-frame f_trace

setprofile must see PY_RETURN/PY_YIELD even when f_trace is unset.
Drop EXPECTED_FAILURE from the pdb doctest and unxfail
test_unfinished_generator.

Assisted-by: Grok:grok-4.6
2026-09-21 18:11:32 +09:00
Jeong, YunWon 617c4be5d9 host_env: export remaining Win32 exception and file constants (#8757)
* host_env: export remaining Win32 exception and file constants

Assisted-by: Claude

* _winapi: take COPY_FILE_DIRECTORY and FILE_TYPE_UNKNOWN from host_env

Assisted-by: Claude
2026-09-21 16:20:39 +09:00
Jeong, YunWon 54420c4471 Drop serial MP CI retries and killpg overlay (#8756)
Multiprocessing fork/spawn/forkserver now run in the main -j $CORES
CPython test job. Remove expectedFailureIf for os.killpg now that
killpg exists.

Assisted-by: Grok:4.6
2026-09-21 12:19:14 +09:00
Hanjeong Lee e189ca7923 Compare native signatures and docstrings in whats_left.py (#8751)
* Record a module's docstring mismatches on their own

They were only kept when the module also had missing items or signature
mismatches, so annotationlib, base64, ntpath and _ctypes reported none.
Whether a module counts as implemented still depends on missing items
and signatures alone.

Assisted-by: Claude Code:claude-opus-5

* Report a builtin type method that differs as a mismatch

A method whose signature or docstring differed was listed under
"builtin items" as if it were missing, with both extra_info dicts as the
reason. That section feeds the website's builtin_items.csv. Differences
now go to mismatched_items and mismatched_doc_items, keyed by type name,
like module items. Nothing reaches this path yet because builtin
signatures are still skipped; without this change, lifting that skip
turns the one "builtin items" line into 455.

Assisted-by: Claude Code:claude-opus-5

* Compare signatures and docstrings of native callables

extra_info() skipped anything inspect treats as builtin since cc2c46b1b
(#2410), when RustPython had no __text_signature__ for builtins. It has
one now, so --signature and --doc cover functions and methods written in
Rust. Modules with a mismatched signature go from 45 to 134, and 160 of
the 710 mismatched items are ones CPython itself cannot parse, which the
--signature listing already skips.

__builtins__ is no longer compared. inspect.signature() evals defaults in
the owning module's namespace and eval() adds __builtins__ there, so
CPython's builtins module gained one after list.index was inspected and
it showed up as missing.

Assisted-by: Claude Code:claude-opus-5

* Scan the members of native classes in each module

A module was only compared one level deep, so a native class counted as
present whatever its methods were. Classes a module owns and that are
not Python-defined are now opened too, and their own members are
compared as "Class.member": 438 classes and 4,833 members, 1,033 of
which have a signature --signature reports.

A class is native when it has no __firstlineno__, which a class
statement records, and no Python functions of its own, which rules out
namedtuple-style classes made by type(). BUILTIN_TYPES moves to module
level so the builtin types are not reported twice, an alias like
array.ArrayType is skipped, and CPython's __annotations_cache__ and
__vectorcalloffset__ are left out.

RustPython follows the CPython list instead of deciding again. A member
only inherited is listed as missing with "(inherited)", as builtin type
methods are. A class missing here, or implemented as something else such
as _sha3's hash functions, is reported by its own line only.

Assisted-by: Claude Code:claude-opus-5

* Report a module that exists but cannot be imported

dir_of_mod_or_error() ran dir() on the exception import_module() returns,
so a failed import looked like a module whose every item is missing:
sqlite3 alone filled 198 of those lines, and CPython's own _ios_support
was compared as a module holding ImportError's attributes. Returning the
exception reaches the "exists but not importable" line that was already
written for it, and drops 529 lines from "stdlib items".

Assisted-by: Claude Code:claude-opus-5

* Compare a module's own docstring

extra_info() reports a docstring for callables, and a module is not one,
so the __doc__ item both sides record was None and a module docstring
never counted as a mismatch. Reading it with getdoc(), the way a
callable's is read, brings 221 module docstrings into the comparison.
sys is the one that differs; base64 and imaplib differ here only
because their Lib copies track 3.14.7 while this run used 3.14.6.

Non-callables keep reporting None. getdoc() on a plain value answers
with its type's docstring, which would turn every string constant into
a mismatch.

Assisted-by: Claude Code:claude-opus-5

* Decide a member's ownership by the class dictionary

attr_is_not_inherited() compared the attribute's value with each base's.
That took an inherited classmethod for the class's own, since a bound
method compares unequal across classes, and dropped an own attribute
whose value matched a base's default None: 394 of CPython's in all,
ast.FunctionDef.returns among them. On the RustPython side it called
AttributeError.name, a class attribute set to None, inherited.

Ownership now means the class dictionary holds the name. An inherited
member is reported only when what it resolves to differs from CPython's
own, so dict_items.__doc__, a slot CPython fills with None that
RustPython resolves to None without one, stays quiet. "(inherited)"
lines drop from 196 to 54, and ast.alias.end_lineno is among the real
gaps that appear.

Assisted-by: Claude Code:claude-fable-5-1
2026-09-20 18:39:44 +09:00
Jeong, YunWon 27647e1e4a Add _thread.get_native_id (#8754)
Return the kernel thread id (gettid / pthread_threadid_np /
GetCurrentThreadId). threading.get_native_id and Thread.native_id
follow once the function exists.

Assisted-by: Grok:4.6
2026-09-20 18:38:28 +09:00
Jeong, YunWon e0df2a10f0 Update malachite to 0.12 (#8755)
malachite-bigint 0.12.0 was published while the workspace pinned 0.11.0.
pymath requires malachite-bigint = "0", so a fresh resolve picks 0.12 for
it and leaves the workspace on 0.11, putting two incompatible BigInt types
in one graph. example_projects carry no lockfile and resolve fresh.

malachite-q and malachite-base move with it. No source change is needed.

Assisted-by: Grok:grok-4.6
2026-09-20 18:37:36 +09:00
YujinBae 587880b9e1 Read decimal digit values from the bundled character database (#8748)
`char_to_decimal` carried its own sorted table of 590 decimal digit
characters, added in #5643 because `common` had no way to reach the
Unicode numeric data at the time: it lived in `stdlib`, which sits above
`common` in the dependency graph. The table's own comment said as much —
"this should probably live in a crate like unic or unicode-properties".

#8211 extracted that data into `rustpython-unicode`, below `common`, and
made `common` depend on it, so the table can now be dropped in favour of
`Ucd::decimal` — the same source `unicodedata.decimal` consults.

This fixes a CPython incompatibility. The table was last updated for
Unicode 10; the bundled UCD is Unicode 17, which has 180 more decimal
digits across 18 scripts (Hanifi Rohingya, Wancho, Tangsa, Ol Onal,
segmented digits and others). Those were rejected by `int()` and
`float()` even though `unicodedata.decimal` knew their value:

    >>> int('\u{10D33}')        # HANIFI ROHINGYA DIGIT THREE
    ValueError                  # CPython: 3
    >>> unicodedata.decimal('\u{10D33}')
    3

All 590 characters the table covered keep their previous value, so
nothing that already worked changes, and the two lookups can no longer
drift apart on a future Unicode update.

The lookup costs about 1.5ns more per non-ASCII character, since it reads
a property trie and a value table instead of binary-searching one 2.4KB
array: `int()` over a 4000-digit non-ASCII string goes from 12.2 to
13.6 ns/char. ASCII input never reaches it — the sole caller,
`transform_decimal_and_space_to_ascii`, returns early for ASCII strings —
so ordinary numeric parsing is unaffected.

Assisted-by: Claude Code:claude-opus-5
2026-09-20 17:48:22 +09:00
hyoinandout 4c7cc0a30c lzma: treat empty input as a no-op, not end of stream (#8752)
decompress_chunks reports whether the codec signalled LZMA_STREAM_END,
and it short-circuits when the chunker has nothing left to feed. That
early return answered "the stream ended" for the case where no input was
supplied at all, so LZMADecompressor.decompress(b"") claimed a finish the
decoder had never signalled.

The caller latches that answer with `self.eof |= stream_end`, which only
ever flips eof on. A single empty call therefore finished the object for
good: needs_input went false and every later call was rejected by the
`if self.eof` guard with EOFError, before a single compressed byte had
been seen. CPython leaves eof false and needs_input true, and accepts
compressed data handed over by a later call.

  d = lzma.LZMADecompressor()
  d.decompress(b"")   # d.eof was True, now False
  d.decompress(blob)  # was EOFError, now b'data'

The zlib engine returns false from the same short-circuit; lzma had
drifted away from it. bz2 was fixed incidentally by #8638 when its engine
moved into crates/common, so only lzma needed a code change.

Verified on macOS arm64 against CPython 3.14.7: the script from the issue
now prints the documented "False True" and b'data' for both
decompressors. test_decompressor_chunks_empty in Lib/test/test_lzma.py
covers exactly this and no longer needs its expectedFailure marker. The
added engine test is named after the zlib one that already existed and is
mirrored into bz2, so all three engines stay pinned to the same
behaviour. test_lzma, test_bz2, test_zlib, test_gzip, test_tarfile,
cargo test --workspace, cargo fmt --check and cargo clippy
-p rustpython-common --all-features --all-targets -D warnings are clean.

Fixes #8637

Assisted-by: Claude Code:claude-opus-5
2026-09-20 17:22:27 +09:00