type_slot_names reads __slotnames__ from the type's own dict before
calling copyreg._slotnames, and rejects a cached value that is not a
list or None. object_getstate_default no longer replaces errors from
type_slot_names with TypeError("cannot pickle object"), so a
RecursionError raised there propagates.
Assisted-by: Claude:claude-opus-5-5
* Address review findings on member descriptors
Member definitions carry an internal `PY_ATOMIC` flag taken from
`MemberLayout::ATOMIC`, and loads use an atomic access only for atomic
storage; extension members never set it. `XMLParser` publishes its new
state, `entity` and `target` only after every handler lookup succeeds.
`subs_parameters` reads type names only for the error. The unraisable
hook prints `<unknown>` when `__module__` fails or is not a str. The
builtin function `__reduce__` decides on the bound object alone.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Fix member layout assertions on 32-bit targets
The tp_flags assertion compared AtomicU64 with u64 alignment, which differs
on i686. The kevent assertion required usize, isize, c_ulong and c_long to be
64-bit. Both now check only what the member cells need.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Use bitflags for member definition flags
PY_READONLY, PY_AUDIT_READ, PY_RELATIVE_OFFSET and PY_ATOMIC become the
repr(transparent) PyMemberFlags. C extension flags are read with
from_bits_retain.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
Use `//` instead of `///` on Python-exposed items so their comments are not
published as `__doc__`.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* fix(contextvars): isolate context stack per interpreter and thread on VirtualMachine
* style: format and sort imports in extra_tests/snippets/stdlib_contextvars.py
Assisted-by: Antigravity:Gemini-3.8-Flash
A Cargo.lock change no longer forces the cross-target cargo check by
itself. The check runs when the lock diff adds lines, or when a removed
package is still referenced from Rust sources.
Assisted-by: Claude Code:claude-opus-5-5
* Read type.__module__ from the dict only for heap types
A static type's `__module__` is the text before the last `.` of its name,
or `builtins`. A heap type reads its dict and raises `AttributeError`
when the entry is missing; `type` and `object` reprs ignore that error.
`type()` no longer stores `__module__ = None` when the caller's globals
have no `__name__`.
`#[pyexception(module = ...)]` now reaches the type name, and static
types that relied on a dict `__module__` (ast nodes, pyexpat, ssl,
_hashlib and _interpchannels errors, ExceptionGroup) carry their module.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Expose more builtin attributes as member descriptors
`code.co_flags`, `co_nlocals`, `co_filename`, `super.__self__`,
`__self_class__`, `__thisclass__`, `traceback.tb_frame`, `tb_lasti`,
`type.__itemsize__` and `builtin_function_or_method.__module__` are now
`#[pymember]` fields instead of getsets. `super` keeps its three
references in atomic cells instead of a lock, `tb_lasti` is an `i32`,
`co_nlocals` is stored at construction, and the builtin function
`__module__` is a writable object cell.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Expose stdlib attributes as member descriptors
Decompressor `eof`/`needs_input`/`unused_data` (bz2, lzma, zlib, plus
lzma `check`), `ContextVar.name`, `functools.partial` `func`/`args`/
`keywords`, socket `family`/`type`/`proto`, `select.kevent` fields,
`SemLock` `handle`/`kind`/`maxvalue`/`name`, `Pickler.bin`/`fast`,
`xmlparser.intern`, ElementTree `XMLParser.entity`/`target`,
`UCD.unidata_version`, `_ExceptHookArgs` fields, multibytecodec
`stream`, and `_io` `Buffered*.raw` and `TextIOWrapper`
`buffer`/`encoding`/`line_buffering`/`write_through` are now
`#[pymember]` fields instead of getsets.
Values that changed under a lock move to atomic cells on the payload.
New member kinds `Short`, `Long`, `String`, `UShort`, `ULong`,
`LongLong` and `ULongLong` access exactly the width of their C type.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Match bare super and static type itemsize
`super.__new__(super)` leaves `__thisclass__`, `__self__` and
`__self_class__` empty, and a super with no type takes the
zero-argument path when bound.
Static types carry their `tp_itemsize` (`type`, `memoryview`, `code`,
`frame`, generators, `_sre` objects, `_ctypes` thunks), inherit it from
their direct base when they leave it at 0, and a variable-size base
rejects any nonempty `__slots__` and a weakref slot.
`_thread._ExceptHookArgs` is a struct sequence.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Expose SimpleNamespace.__dict__, __basicsize__ and __flags__ as members
`SimpleNamespace.__dict__` addresses the instance dict cell the same way
`module.__dict__` does. `slots.basicsize` now holds the full
`tp_basicsize` (header plus payload), so `__basicsize__` reads it
directly. Type flags are one atomic word: `__abstractmethods__` and
`_abc` set their bits in it in place, the separate `abc_tpflags` word is
gone, and `__flags__` reads the word as an unsigned long member.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Skip Python-defined classes when collecting builtin type docs
generate.py recorded every object.__subclasses__() entry as builtins.<name>
after importing the whole stdlib, so classes written in Python (tkinter,
unittest, tarfile, ...) ended up in the doc database. Classes that are
mutable heap types without C method descriptors and whose module is not a
C extension are now skipped, and their entries are removed from
data.inc.rs.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Store database docstrings xz-compressed behind an optional doc feature
- rustpython-doc's build script compresses all database docstrings into
one xz blob with an offset index; identical texts share one offset.
- Macros emit blob references for database docs; vm decompresses the
blob on first access.
- New `doc` cargo feature (default on) in rustpython, rustpython-vm and
rustpython-stdlib. Without it the blob and xz are not linked and
database-backed __doc__ is None; __text_signature__ is unaffected.
- Native types without a docstring get __doc__ = None in their dict.
- Workflows that build with --no-default-features enable `doc` where
tests or releases need docstrings.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Raise MemoryError instead of aborting on impossible allocations
Assisted-by: Claude Code:claude-opus-5-5
* use CPython 3.14's integer digit limit for left shifts
* size the pow memory check by what malachite allocates
* Make sequence allocations fallible and reuse deallocation buffers
Assisted-by: Codex:GPT-6
---------
Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com>
* Address builtin members by field offset
`#[pymember]` now declares a field of the payload and generates a
PyMemberDef whose offset is the payload offset plus the field offset.
The getter/setter function form and `MemberAccess::Func` are removed;
builtin, slot, and C-API members share one get/set path. The macro
checks the field type against the member type.
Converted members: property fget/fset/fdel/__doc__, function
__globals__/__closure__/__builtins__/__doc__/__module__, staticmethod
and classmethod __func__/__wrapped__, complex real/imag (T_DOUBLE),
frame f_trace_lines (T_BOOL), defaultdict default_factory, getset and
member descriptor __name__/__objclass__, module __dict__.
frame.f_trace_opcodes is a getset.
Writable member fields are atomic pointer cells. The instance dict
pointer is an atomic cell at a fixed offset in front of the object,
and dict presence follows the type's HAS_DICT flag. The pickle
basicsize check no longer counts dict, weakref, and slot storage,
which live outside the payload.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Address member review findings
- `#[pymember]` keeps leading `#[cfg]` attributes on the generated
registration, and malformed `field`/`offset` values are compile
errors.
- The `descriptor` module and the items the expansion names are public.
- Writable double members are an `AtomicU64` cell, loaded and stored
with relaxed atomics. Readonly double members stay plain `f64` reads.
- `LOAD_ATTR_SLOT` is not specialized for `Py_AUDIT_READ` members.
- `defaultdict.__reduce__` passes no factory argument when
`default_factory` is None.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Declare members on payload fields
`#[pymember]` goes on a payload struct field, or on the struct with an
explicit `offset` when the storage is not a payload field. The struct
`#[pyclass]` builds `PyClassDef::MEMBERS`, a static `PyMemberSpec`
table, and `extend_class` registers one member descriptor per entry.
`path` addresses a subfield, one field may carry several members, and a
`#[cfg]` on the field gates its entry. The impl-side const form is
removed and now a compile error.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Split nullable and non-null atomic cells
The nullable cell API (`new_empty`, `load_owned`, `store`,
`compare_exchange_empty`) moves from `PyAtomicRef<PyObject>` to
`PyAtomicRef<Option<PyObject>>`, and the typed `PyAtomicRef<Option<T>>`
shares it. The non-null `PyAtomicRef<PyObject>` gets `load_owned` and
`store` that return the object directly.
Slot cells, property fget/fset/fdel/__doc__, and defaultdict
default_factory are nullable cells. staticmethod and classmethod
callable are non-null cells and are read without `unwrap`. The
instance dict cell is `PyAtomicRef<Option<PyDict>>`, so reading it no
longer downcasts.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Drop the unused cold trace_lines flag
`FrameColdData::trace_lines` had no writer after `f_trace_lines`
became a frame object member. A frame without a frame object traces
lines by default.
Assisted-by: Claude:claude-opus-5-5
* Compute payload offsets from the merged Py header
`PyInner` is now `Py`, so the payload offset and the dict member
offset are measured from `Py<T>`.
Assisted-by: Claude:claude-opus-5-5
* Expose descriptor names and owner types as members
method_descriptor, classmethod_descriptor, and wrapper_descriptor
expose `__objclass__` and `__name__` as readonly members, and
method-wrapper exposes `__self__` as a readonly member. The separate
`objclass` field of method and classmethod descriptors always equaled
`common.typ` and is removed.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Expose remaining builtin tp_members as members
Attributes stored in a payload field become readonly or writable
member descriptors: bound method __func__/__self__, code
co_argcount/co_posonlyargcount/co_kwonlyargcount/co_stacksize (Py_T_UINT),
generic alias, coroutine and async generator members, interpolation and
template, range and slice start/stop/step, type __base__, union, and the
typing TypeVar, ParamSpec, TypeVarTuple, and TypeAliasType members.
`MemberKind` gains `Int` and `Uint` with the `PyMember_GetOne`/`SetOne`
range checks.
Attributes computed on access or kept behind a lock stay getsets.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Spell a docless member as doc = false
`#[pymember(doc = false)]` stores no docstring, matching the
`doc = "..."` form, and replaces the separate `no_doc` flag.
Assisted-by: Claude:claude-opus-5-5
* Make pymember readonly by default
`#[pymember]` declares a readonly member unless `writable` is given.
The `readonly` option is removed and every declaration is converted.
Assisted-by: Claude:claude-opus-5-5
* Check the receiver of method descriptors
Binding or calling a method_descriptor with an object that is not an
instance of the owner type raises TypeError before the native function
runs. Static and class methods are exempt.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Expose exception attributes as members
BaseException.__suppress_context__, StopIteration.value, SystemExit.code,
ImportError, NameError, AttributeError, OSError, SyntaxError,
BaseExceptionGroup and the UnicodeEncode/Decode/TranslateError attributes
are member descriptors backed by payload fields. ImportError, NameError,
AttributeError and SyntaxError gain payloads for their fields, and the
methods that read those attributes read the fields. Add the Py_ssize_t
member kind for UnicodeError start and end.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Avoid same-type casts in unsigned member conversion
On targets where c_ulong is u32, casting through c_ulong was a no-op
cast that clippy rejects.
Assisted-by: Claude:claude-opus-5-5
* Prefer an explicit pymember doc over the doc table
doc = "text" is used as the docstring without consulting the stored
attribute documentation; only an omitted doc looks it up. Drop the member
doc strings that repeated the stored documentation.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Infer pymember kinds from field types
MemberLayout gives the member kind of each accepted field type, and the
macro reads it from the field or path subfield, so type = "bool" and
the other numeric spellings are gone. type only selects object_ex.
Writable doubles use a new AtomicF64 cell instead of AtomicU64 bits.
MemberLayout and MemberCell replace the per-kind marker traits.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Read nullable atomic cells only through load_owned
PyAtomicRef<Option<T>> and PyAtomicRef<Option<PyObject>> lose to_owned,
to_owned_ordering and try_to_owned; load_owned, which retries the
incref until it applies to the pointer still in the cell, is the one
owned read. Callers that kept a deref borrow across VM calls now take a
strong reference.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Inherit static type slots from the whole MRO
extend_class copied slots only from the direct bases, and tp_init is
copied only from a base that defines it, so TabError missed the
SyntaxError init that IndentationError inherits. Walk the MRO the same
way init_slots does.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Let Rust doc comments win over stored docstrings
A /// comment on a Python-exposed item is its docstring; the
rustpython-doc entry is used only when there is none. #[pymember] reads
the field's /// comment instead of doc = "...". Doc comments that were
shadowed by a stored docstring become // comments, so no docstring
changes. AGENTS.md describes the rule.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Adapt members to upstream changes
_collections._tuplegetter.__doc__ is a writable nullable member on the
payload field. The impl-side const form no longer compiles.
Assisted-by: Grok:4.7
* Drop docstring text left as comments and inline downcast_cell
Comments that only repeated an item's docstring are removed; developer
notes stay as // comments. The typed cell methods call
downcast_unchecked directly, each with its own safety note.
Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
* Read sqlite row factories through load_owned
The nullable cell no longer has to_owned; the sqlite module is built only
with the sqlite feature, so the default build did not catch it.
Assisted-by: Claude:claude-opus-5-5
Interned strings live in the process-wide Context and cache their hash.
Each top-level interpreter built its own HashSecret from its hash_seed,
so a second interpreter with a different seed looked up cached hashes
computed with the first secret and failed bootstrap with
`KeyError: _frozen_importlib`.
The secret is now a process-wide OnceLock set by the first top-level
interpreter; later seeds are ignored. The per-interpreter hash_secret
field and the check in VirtualMachine::new are removed.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Read PyTuple payloads through as_slice()
- Add Py<PyTuple<R>>::as_slice(); it is generic over R, so typed tuples use
the same view.
- Callers reach slice methods, indexing and slice arguments through
as_slice().
- PyTuple's own sequence and mapping slots and marshal's set_marshal_item reach
the remaining PyTuple methods through the payload field.
- The Py<T>::payload field is pub(crate).
Assisted-by: Claude Code:claude-opus-5-5
* Read PyBytes payloads through as_bytes()
- Callers read bytes through Py<PyBytes>::as_bytes().
- bytes.rs reaches PyBytesInner and the remaining PyBytes methods through
the payload field; fspath and os call payload.contains_nuls().
- DictKey for [u8] falls back to the PyObject key_eq; it previously resolved
to its own key_eq through Deref.
Assisted-by: Claude Code:claude-opus-5-5
* Copy capi tuple arguments with as_slice().to_vec()
Assisted-by: Claude Code:claude-opus-5-5
* Read sqlite3 Row tuples through as_slice()
Assisted-by: Claude Code:claude-opus-5-5
* Read platform-specific tuples and bytes through as_slice() and as_bytes()
Assisted-by: Claude Code:claude-opus-5-5
* Read PyInt and PyFloat payloads through as_bigint() and to_f64()
Add `Py<PyInt>::as_bigint()`, `Py<PyInt>::try_to_primitive()` and
`Py<PyFloat>::to_f64()`, which forward to the payload methods of the same
name. Reads of the `value` field and primitive conversions go through
these views; other PyInt and PyFloat methods are called through the
`payload` field inside rustpython-vm and through payload() outside it.
Assisted-by: Claude Code:claude-opus-5-5
* Read PyComplex payloads through as_complex()
Add Py<PyComplex>::as_complex() and use it at the call sites that
read the payload directly.
Assisted-by: Claude Code:claude-opus-5-5
* Move PyInt conversion helpers to Py<PyInt>
try_to_i64_fast(), to_str_radix_10(), as_u32_mask(), as_u64_mask(),
try_to_primitive() and try_to_primitive_raw() only read the integer
value, so they are defined on Py<PyInt> and callers no longer go through
the payload. PyRange computes its protocol length from the BigInt
directly instead of building a PyInt payload.
Assisted-by: Claude Code:claude-opus-5-5
* Share one conversion between try_to_primitive() and its OverflowError variant
Py<PyInt>::try_to_primitive() and try_to_primitive_in_range(), renamed
from try_to_primitive_raw(), both call to_primitive(). They differ only
in how a negative value for an unsigned type is reported: ValueError for
try_to_primitive(), OverflowError for try_to_primitive_in_range(). The
target type is bound with `for<'a> TryFrom<&'a BigInt>`, so the methods
no longer tie a lifetime to &self.
Assisted-by: Claude Code:claude-opus-5-5
* Move PyInt bitwise operators to Py<PyInt>
__xor__, __or__, __and__ and their int_op() helper are defined on
Py<PyInt>. PyBool calls them without going through the payload.
Assisted-by: Claude Code:claude-opus-5-5
* Move PyBytes operators to Py<PyBytes>
__add__, __contains__, __getitem__ and __mod__ are defined on
Py<PyBytes>. The subscript, contains and remainder slots call them
without going through the payload.
Assisted-by: Claude Code:claude-opus-5-5
* Move PyInt general_op() and modpow() to Py<PyInt>
The power slot calls them on Py<PyInt> without going through the
payload.
Assisted-by: Claude Code:claude-opus-5-5
* Accept `__index__` objects for integer arguments
CPython converts these nine arguments with `__index__`, RustPython
required an exact `int`. Uses the existing `ArgSize`/`ArgPrimitiveIndex`
converters at each call site rather than widening the blanket `int`
conversion, so argument paths CPython keeps strict (ctypes pointers,
thread handles) are unaffected, and the range check happens while the
argument is bound rather than in the method body.
Also widens `maxsplit` before adding one. `sys.maxsize` was already
reachable there with a plain `int` and overflowed in a debug build.
Assisted-by: Claude Code:claude-opus-5
* Add snippet for `__index__` argument conversion
Expectations verified against CPython 3.14.
Assisted-by: Claude Code:claude-opus-5
* Call __index__ in primitive integer conversion; add ArgStrictInt
The TryFromObject impls for isize, usize and the fixed-width integer
types now take an int (or subclass) as-is and otherwise call __index__,
instead of accepting only int instances.
ArgStrictInt<T> keeps the old int-only conversion. The ctypes address
and handle arguments (from_address, PyObj_FromPtr, dlsym, dlclose,
call_function, call_cdeclfunction, non-Windows FreeLibrary) use it.
_thread._make_thread_handle checks for an int itself and raises
"ident must be an integer".
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Accept __index__ where arguments were bound as int objects
chr, bin, math.factorial, zlib.crc32/adler32, binascii.crc32/crc_hqx,
the start/stop of sequence index(), the select event mask and a
__len__ result took an int object directly and rejected other objects
with __index__. They now go through __index__.
math.ldexp still accepts only an int for its exponent and raises
"Expected an int as second argument to ldexp." otherwise.
Extend protocol_index_argument.py with the newly accepted calls, an int
subclass whose __index__ is not called, and the sites that still
reject __index__.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Replace ArgPrimitiveIndex and ArgSize with PySsize/PySize aliases
The primitive integer conversions call __index__ themselves, so
ArgPrimitiveIndex<T> and its alias ArgSize behaved the same as T. They
are removed. Arguments that used ArgSize or ArgPrimitiveIndex<isize>
now take PySsize (= isize), ArgPrimitiveIndex<usize> takes PySize
(= usize), and the other ArgPrimitiveIndex<T> take T. Those with a
literal fallback take it as a `default`; those where a missing argument
is a distinct state stay OptionalArg.
list.insert is a #[pymethod] again instead of going through a wrapper.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
---------
Co-authored-by: Roshan Ramani <roshanramani.dev@gmail.com>