* Report errors from #[pyclass] trait items
The trait branch of impl_pyclass_impl built its own ImplContext, so the
errors collected while extracting trait items were never emitted: an
invalid #[pymethod] or #[pygetset] in a #[pyclass] trait was left out of
the class without a compile error. The trait branch now uses the shared
context whose errors are emitted.
Assisted-by: Claude Code:claude-opus-5-5
* Pass the object to #[pymethod] and #[pygetset] receivers outside vm
Outside crates/vm, where impl Py<T> cannot be written, #[pymethod] and
#[pygetset] receivers that took &self become zelf: &Py<Self> inside the
existing impl T blocks. Where Rust code calls one of these methods on a
payload value, the logic stays in a payload method and the Python method
forwards to it.
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Reject payload receivers on #[pymethod] and #[pygetset]
A #[pymethod] or #[pygetset] item with a self receiver is now a compile
error when self is the payload: in an impl for T rather than Py<T> or
PyRef<T>, and in a #[pyclass] trait. The error points to taking
zelf: &Py<Self> or zelf: PyRef<Self>, or defining the item in impl Py<T>.
Assisted-by: Claude Code:claude-opus-5-5
* Pass the object to the layout test getter
The subclass layout test's extra getter is a #[pygetset] on the
payload. Take zelf: &Py<Self> so the receiver is the object.
Assisted-by: Grok:4.7
* 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
* 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
* Render CPython defaults in generated text signatures
Arguments whose default rendered as `<unrepresentable>`, while the
same callable has a representable default in CPython, now render that
default:
- Where the Rust value can be a literal, the argument becomes a plain
field with `default = <literal>`.
- The remaining cases keep their Rust default and add `py_default`
with a comment. These are wrappers such as ArgIntoFloat or
ArgIntoBool, values that need the VM, enums, and Option where None
means another value.
Byte string literals render as Python bytes. A function registered
under several names gets an internal doc per name, so its __doc__ no
longer carries the signature header.
Removed expectedFailure from test_pydoc tests that now pass.
Assisted-by: Grok:grok-4.7
Assisted-by: Claude:claude-opus-5-5
* Match native callable signatures to CPython names and kinds
Rename parameters and adjust positional-only / keyword-only kinds and
defaults of native functions and methods so their generated text
signatures match CPython. Remove expectedFailure markers from tests
that now pass.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Fix Windows console read size and keep type.__prepare__ permissive
Assisted-by: Claude:claude-opus-5-5
* Derive signature defaults from typed Rust values
Param.default is now a DefaultRepr rendered by the const writer.
Literals, integer and bool constants become typed defaults, and other
default expressions use the type's const fn py_default. A bare
`optional` renders as None, so `default = None` rewrites are reverted.
Address review comments: zlib max_length defaults, get_ca_certs
binary_form keyword, getpeercert der, WASM ssl certificate keywords,
fstatvfs fd-only, and _sre endpos shown as sys.maxsize.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Generate class constructor text signatures at compile time
A #[pyclass] impl composes the type's internal doc from the Args of
its Initializer or Constructor, so type.__text_signature__ and
inspect.signature work for native classes. __doc__ stays the plain
doc. Struct sequences get (iterable=(), /) and __reduce__ (self, /).
Hand-parsed constructors are converted to FromArgs structs, and
_weakref.proxy is split from ProxyType.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Remove expectedFailure from test_hmac tests that now pass
Assisted-by: Claude:claude-opus-5-5
* Match remaining native signatures and count missing CPython signatures as matches
Give lock.__enter__ and RLock.__enter__ their own no-argument methods,
add FromArgs for start_new_thread and start_joinable_thread keeping
their errors, take RTLD_GLOBAL/RTLD_LOCAL from libc, and align names,
kinds and <unrepresentable> defaults where CPython's signature is
invalid (bytes.hex, reduce, warn, link, setlocale, add_done_callback,
...). getaddrinfo accepts keyword arguments. whats_left treats a
callable CPython has no signature for as matching.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Drop unfulfilled dead_code expectation on os.link dir_fd args
Assisted-by: Claude:claude-opus-5-5
* Distinguish OptionalArg and Option in signature defaults
An `optional` field's signature default now comes from its type:
Option<T> renders None, while OptionalArg<T> (a missing argument that
is not any Python value) renders <unrepresentable>. Fields where a
missing argument behaves exactly like None become Option<T>; fields
whose missing state differs keep OptionalArg. Redundant
py_default = "<unrepresentable>" entries are removed.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Remove expectedFailure from test_rlcompleter.test_attr_matches
Assisted-by: Claude:claude-opus-5-5
* Use Option for optional arguments whose missing and None states match
OptionalOption and OptionalArg<Option<T>> fields that treat a missing
argument like None become Option<T> with #[pyarg(optional)], which renders
=None in the text signature. Plain optional parameters of that kind move
into small FromArgs structs. Uses where missing and None differ (reduce
initial, StringIO newline, TextIOWrapper.reconfigure, set_asyncgen_hooks)
keep OptionalOption.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Render ::NAME defaults by name and restore named constant defaults
- default = ::NAME writes NAME into the text signature and stores
Into::into(NAME) as the Rust value.
- A one-field tuple-struct argument pattern such as Fildes(fd) names the
parameter after its binding, so fcntl and termios keep destructuring.
- zlib, lzma, syslog, mmap and select defaults use their named constants
again instead of literals or <unrepresentable>.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Name FromArgs fields after their Python parameters
Drop #[pyarg(name = ...)] where the Python name is a valid Rust field name
and rename the field instead. The option stays only for keywords (fn, loop,
final, type, self) and the non-snake_case memLevel.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Build literal defaults from Python values and type <unrepresentable>
- default = LIT on a non-primitive field converts the literal through
ToPyObject and TryFromObject when the argument is missing, so object
fields no longer need a vm expression plus a matching py_default.
- A bare default on a primitive integer, bool or float field renders its
zero value.
- DefaultRepr::Unrepresentable replaces the <unrepresentable> text, and
py_default = "<unrepresentable>" is a compile error.
- pyexpat.ParserCreate distinguishes a missing intern from intern=None,
mmap AccessMode renders its value, and sqlite blobopen names rowid.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Resolve native docstrings from the doc database at compile time
- rustpython-doc stores its table as a sorted const slice with const fn
lookups (get, get_attr, class_attr_doc) instead of a phf map, and the
update-doc-db workflow emits that form.
- Class, method, getset, module function and struct sequence docs, and
per-class slot wrapper docs, are chosen by const lookups in the derive
output. The database text wins over Rust doc comments.
- Macros reach the table through rustpython_vm::__exports, so only the vm
depends on rustpython-doc.
- Remove expectedFailure markers from doctest and pydoc tests that pass.
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Grok:grok-4.7
* Keep time.gmtime, localtime and ctime without a text signature
Their [seconds] parameter has no text signature, and unittest.mock relies
on inspect.signature failing for them. Take OptionalOption again so the
default stays unrepresentable, and treat None like a missing argument.
Assisted-by: Claude:claude-opus-5-5
* 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
* Add ExceptionGroup fields and OSError use-init
BaseExceptionGroup stores msg, excs, and excs_str; args keep
the original sequence. OSError defers parse when a subclass
overrides only __init__. characters_written can be deleted.
Exception args/traceback/cause/context reject invalid
setattr/delattr. UnicodeError.object is a deletable field.
except* remainder projection copies traceback/context/cause.
SyntaxError.__init__ clears omitted location attributes.
Assisted-by: Grok:grok-4.6
* Fix OSError payload init and UnicodeError construction
new_payload_exception now runs slot_new so BlockingIOError
parses characters_written. new_exception falls back to
invoke_exception for extra-payload types. Mixed tab/space
UnexpectedIndentation is TabError. AttributeError pickle
omits obj.
Assisted-by: Grok:grok-4.6
* address review: classify TabError from error line
Assisted-by: Grok:grok-4.6
* Add new_simple_exception; fill SyntaxError and f_lineno
new_exception constructs extra-payload types; BaseException-sized
types use new_simple_exception. Non-UTF-8 SyntaxError sets location
and text. Codegen errors load text via ProgramText. Keep Missing
parentheses capitalization. f-string adjacent atoms report a missing
comma. f_lineno may be None; lasti is not restored on unwind.
Assisted-by: Grok:grok-4.6
* address review: skip f-string continuation keywords
Assisted-by: Grok:grok-4.6
* Fix clippy map_unwrap_or in Non-UTF-8 offset
Assisted-by: Grok:grok-4.6
* Rename encode error ctor and exception setters
new_unicode_encode_error_real is new_unicode_encode_error.
set___traceback__/__cause__/__context__ are pygetset setters;
Rust uses set_traceback/set_cause/set_context.
Assisted-by: Grok:grok-4.6
* Use set_context in native pickle
Assisted-by: Grok:grok-4.6
* Collapse nested if in asyncio traceback restore
Assisted-by: Grok:grok-4.6
* Use current instruction for f_lineno addr2line
lasti is stored as the next instruction index. addr2line of
that byte offset is past RETURN_VALUE, so f_lineno was None
and stdlib_threading_current_frames hung on leftover workers.
Assisted-by: Grok:grok-4.6
* Look up format args, dir class, and frame builtins
Consume printf args before rejecting unknown conversions.
object.__dir__ uses getattr of __class__/__dict__.
_PyEval_GetBuiltin looks up iter/__import__ on the frame.
Assisted-by: Grok:grok-4.6
* Fix builtin lookups after rebase and unxfail passing tests
Assisted-by: Grok:grok-4.6
* Restore RERAISE lasti and SyntaxError locations
RERAISE with oparg restores lasti from the exception table
before unwind, so leftover f_lineno matches the original raise.
Handler lookup still uses the raising instruction.
Indentation errors outrank print/exec missing-parentheses.
BOM-with-non-utf8, bytes literals, and global+nonlocal now
report the same offsets as the parser/symtable.
object.__dir__ keeps string keys; type.__type_params__ skips
the getset wrapper stored on builtin types.
Assisted-by: Grok:grok-4.6
* Unxfail syntax/import tests and bind jump locals
IMPORT_NAME passes fromlist None instead of an empty tuple.
Jumping f_lineno binds None into unbound localsplus slots.
str lstrip/rstrip copy subclasses to exact str when unchanged.
Assisted-by: Grok:grok-4.6
* Fix import_from callers and rename frame lineno
sqlite3.iterdump now passes fromlist as a PyObject.
FrameObject::lineno is the i32 helper; f_lineno is the getset.
Assisted-by: Grok:grok-4.6
* Pass sqlite fromlist without Into conversion
from_list.into() could not infer Into<PyObjectRef> at the
import_from call. The typed tuple already converts.
Assisted-by: Grok:grok-4.6
* sqlite3: fix iterdump import and filter support
Fix Connection.iterdump() to import sqlite3.dump with a from-list and forward its keyword-only filter argument, matching CPython 3.14. Remove stale expected-failure markers from all passing iterdump tests.
Assisted-by: GitHub Copilot:GPT-5.6 Luna (high)
* sqlite3: fix clippy core iterator lint
Use core::iter instead of std::iter in the iterdump filter kwargs path, as required by RustPython clippy. Add the sqlite3 expected-failure workflow and CI clippy feedback reports.
Assisted-by: GitHub Copilot:GPT-5.6 Luna (high)
Match CPython 3.14 by distinguishing row_factory assignment from deletion and raising AttributeError with the compatible message. Unmark the passing row_factory deletion test.
Assisted-by: GitHub Copilot:GPT-5.6 Luna (high)
* sqlite3: add SQLITE_DBCONFIG constants, setconfig() and getconfig()
Implement Connection.setconfig() and Connection.getconfig() using
sqlite3_db_config(), and export all SQLITE_DBCONFIG_* integer
constants to the module.
Fixes test_connection_config in test_dbapi.py.
Assisted-by: GitHub Copilot:claude-sonnet-4-6
* sqlite3: fix setconfig to propagate actual SQLite error
When sqlite3_db_config() returns a non-OK result, propagate the actual
SQLite error instead of replacing it with a generic 'Unable to set
config' message, to match CPython's set_error_from_db() behavior.
Assisted-by: GitHub Copilot:claude-sonnet-4-6
* sqlite3: pass None for NULL authorizer args instead of crashing
CPython's authorizer callback receives NULL for arg1/arg2/db_name/access
when not applicable (e.g. SQLITE_READ on a table gives NULL for the
database name in some versions). Previously RustPython passed these
pointers to ptr_to_str which would crash or produce an error.
Now ptr_to_str_or_none is used: NULL pointers become Python None, which
matches CPython behavior and allows test_table_access and
test_column_access to pass.
Assisted-by: GitHub Copilot:claude-sonnet-4-6
* fix(sqlite3): avoid reentrant deadlock in Statement::new
sqlite3_prepare_v2 can synchronously invoke the authorizer callback,
which may call back into Connection methods (e.g. set_authorizer)
that require the same db lock. Holding db_lock across the prepare()
call caused a self-deadlock when a callback re-entered the connection.
Release the lock after sql_limit check and copy the raw handle before
calling prepare(), so FFI calls that can trigger Python re-entrancy
happen outside the lock scope.
Fixes hang in test_authorizer_concurrent_mutation_in_call
CPython 3.14 added check_num_params() which raises ProgrammingError when
narg/n_arg/num_params is out of range (-1..=SQLITE_LIMIT_FUNCTION_ARG).
RustPython passed invalid values directly to SQLite, resulting in an
OperationalError instead.
Add check_num_params() helper and call it in create_function(),
create_aggregate(), and create_window_function().
Assisted-by: GitHub Copilot:claude-sonnet-4-6
CPython raises ProgrammingError with "Binding N is a named parameter"
when a query uses named placeholders (, , ) but
the caller passes a sequence instead of a mapping.
RustPython's bind_parameters_sequence() did not check whether each
binding slot is a named parameter, so no error was raised.
Fix: call sqlite3_bind_parameter_name() for each slot in
bind_parameters_sequence(). If the first byte of the returned name is
not '?' (i.e. it is a named placeholder), raise ProgrammingError before
attempting to bind.
Assisted-by: GitHub Copilot:claude-sonnet-4-6
Row(cursor, data) raised ValueError when cursor.description was None.
CPython allows this case and returns an empty key list.
- Use empty tuple when description is None instead of raising
- Include the key name in the IndexError when a string key is not found
Assisted-by: GitHub Copilot:claude-sonnet-4-6
When a Connection is explicitly closed via con.close(), subsequent
operations (cursor(), commit(), rollback(), create_function(), etc.)
should raise ProgrammingError with 'Cannot operate on a closed database.'
to match CPython behaviour.
Previously, _db_lock() always returned 'Base Connection.__init__ not
called.' when self.db was None, without distinguishing between a
connection that was never initialised (subclass before __init__) and
one that was initialised and then explicitly closed.
Fix: inspect the initialized atomic flag — if True but db is None,
the connection was closed; if False, it was never initialised.
Assisted-by: GitHub Copilot:claude-sonnet-4-6
* sqlite3: fix Blob.__setitem__ value range validation
Previously, assigning an out-of-range integer (negative or > 255) or an
integer too large for i64 (e.g. 2**65) to a Blob index raised OverflowError
instead of ValueError.
Mirror CPython's ass_subscript_index logic:
- Convert the value via to_i64(), treating any overflow as -1
- Validate the result is in [0, 255], raising ValueError("byte must be in range(0, 256)") otherwise
- Separate deletion error messages: "item deletion" for index, "slice deletion" for slice
* sqlite3: fix Blob.__setitem__ negative-step slice write
In the step != 1 branch of Blob.ass_subscript, the loop used
i_in_temp += step as usize
where step is isize. For negative steps (e.g. step = -2),
(-2isize) as usize = 18446744073709551614
causing an out-of-bounds panic whenever slice_len >= 2.
Fix: use SaturatedSliceIter (already used by the read path) to iterate
over the correct absolute blob indices, then map each index back to a
temp buffer offset via abs_idx - range_start.
Also fix a Clippy lint: replace
val < 0 || val > 255
with the idiomatic
!(0..=255).contains(&val)
Add a regression test in extra_tests/snippets/stdlib_sqlite.py that
exercises blob[9:0:-2] (negative step, slice_len=5).
* fix: guard blob negative-step snippet from CPython 3.11 bug
* style: add blank line after import sys in stdlib_sqlite snippet (ruff)
* Update extra_tests/snippets/stdlib_sqlite.py
---------
Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com>
* Introduce TimeoutSeconds utility type for timeout parameters
Follow-up refactoring from #7237.
Python timeout parameters typically accept both float and int. Several
places in the codebase used Either<f64, i64> for this, each repeating
the same match-and-convert boilerplate. This extracts that into a
TimeoutSeconds type in vm::function::number.
Refactored sites:
- _sqlite3::ConnectArgs.timeout
- _thread::AcquireArgs.timeout
- _thread::ThreadHandle::join timeout
Either<f64, i64> in time.rs (6 sites) left unchanged: those are
timestamp values with per-branch logic (floor, range checks, etc).
Either<f64, isize> in select.rs also left unchanged (different type).
* Validate timeout in ThreadHandle::join to prevent panic
* refactor: move TimeoutSeconds from number to time module
* Object header slimming: prefix allocation for ObjExt
Extract dict, weak_list, and slots fields from PyInner<T> into a
separate ObjExt struct allocated as a prefix before PyInner using
Layout::extend(). Objects that don't need these fields (int, str,
float, list, tuple, dict, etc.) skip the prefix entirely.
- Add HAS_WEAKREF flag to PyTypeFlags for per-type weakref control
- Add HAS_EXT bit to GcBits indicating prefix presence
- Define ObjExt struct with dict, weak_list, slots
- Shrink PyInner header from ~80-88 bytes to ~32 bytes for lightweight objects
- Update all accessor methods to go through ext_ref()
- Update bootstrap type hierarchy to use prefix allocation
- Add __weakref__ getset descriptor for heap types
- Set HAS_WEAKREF on builtin types that support weak references
- Remove test_weak_keyed_bad_delitem expectedFailure (now passes)
* Add HAS_WEAKREF to _asyncio Future/Task, rename weakref helpers
- Add HAS_WEAKREF flag to PyFuture and PyTask (matches CPython)
- Rename subtype_getweakref/setweakref to subtype_get_weakref/set_weakref
to fix cspell unknown word lint
* Add HAS_WEAKREF to array, deque, _grouper; remove expectedFailure markers
- Add HAS_WEAKREF to PyArray and PyDeque (matches CPython)
- Add HAS_WEAKREF to PyItertoolsGrouper (internal use by groupby)
- Remove 6 expectedFailure markers from test_dataclasses for weakref/slots
tests that now pass
* Add HAS_WEAKREF to code, union, partial, lock, IO, mmap, sre, sqlite3, typevar types
Add HAS_WEAKREF flag to built-in types that support weakref:
- PyCode, PyUnion, PyPartial, Lock, RLock
- All IO base/concrete classes (_IOBase, _RawIOBase, _BufferedIOBase,
_TextIOBase, BufferedReader, BufferedWriter, BufferedRandom,
BufferedRWPair, TextIOWrapper, StringIO, BytesIO, FileIO,
WindowsConsoleIO)
- PyMmap, sre Pattern, sqlite3 Connection/Cursor
- TypeVar, ParamSpec, ParamSpecArgs, ParamSpecKwargs, TypeVarTuple
Remove 3 expectedFailure markers from test_descr for now-passing tests.
* Add HAS_DICT to type flags and handle non-METHOD/CLASS in descr_get
- Add HAS_DICT flag to PyType (type metaclass) alongside existing
HAS_WEAKREF. All type objects are instances of type and need dict
support, matching CPython's PyType_Type.
- Replace unimplemented!() in PyMethodDescriptor::descr_get with
fallback to bind obj directly, matching CPython's method_get which
uses PyCFunction_NewEx for non-METH_METHOD methods.
* Fix ext detection, HeapMethodDef ownership, WASM error
- Remove HAS_EXT gc_bits flag; detect ext from type flags
using raw pointer reads to avoid Stacked Borrows violations
- Store HeapMethodDef owner in payload instead of dict hack
- Clear dict entries in gc_clear_raw to break cycles
- Add WASM error fallback when exception serialization fails
Add #[pygetset] getter/setter for Cursor.row_factory so that Python-level
attribute access reads/writes the Rust struct field instead of the
instance dict.
Fix Connection.cursor() to only propagate the connection's row_factory
to the cursor when the connection's row_factory is not None, matching
CPython behavior. Previously it unconditionally overwrote the cursor's
row_factory, discarding any factory set by a cursor subclass __init__.