Address builtin members by field offset (#8816)

* 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
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-09-27 13:38:11 +09:00
1 parent e2ca20882f
commit bdc9246b37
76 files changed
+2329 -1707

No files matched your search

+1 -1
View File
@@ -161,7 +161,7 @@ Run `./scripts/whats_left.py` to get a list of unimplemented methods, which is h
- Do not delete or rewrite existing comments unless they are factually wrong or directly contradict the new code.
- Do not add decorative section separators (e.g. `// -----------`, `// ===`, `/* *** */`). Use `///` doc-comments or short `//` comments only when they add value.
- Do not put `///` doc comments on items annotated with `#[pyattr]`, `#[pyclass]`, or `#[pyfunction]`. The derive macros pull authoritative docstrings from CPython via the `rustpython-doc` crate; a Rust doc comment overrides that source, and on `#[pyattr]` it is silently dropped.
- A `///` doc comment on a Python-exposed item (`#[pyattr]`, `#[pyclass]`, `#[pyfunction]`, `#[pymethod]`, `#[pyclassmethod]`, `#[pystaticmethod]`, `#[pygetset]`, `#[pymember]`, `#[pyslot]`, `#[pyexception]`, `#[pymodule]`, `#[pystruct_sequence]`) is that item's docstring and overrides the stored docs in `rustpython-doc` (`crates/doc`). Those stored docs are used only when the item has no `///` comment. Developer notes on these items must be `//` comments.
#### Avoid Duplicate Code in Branches
+2 -2
View File
@@ -149,8 +149,8 @@ impl PyMemberDef {
flags,
doc,
},
// `offset` is a byte offset from the object to a pointer cell.
access: MemberAccess::Slot,
// `offset` is a byte offset from the object to the field.
access: MemberAccess::Offset,
};
Ok(descriptor.into_ref(&vm.ctx))
+27
View File
@@ -52,6 +52,33 @@ impl<T> PyAtomicScalar for *mut T {
type Radium = atomic_ty!(*mut T, AtomicPtr<T>);
}
/// Atomic `f64`. The value is stored as its bits in an `AtomicU64`.
#[repr(transparent)]
pub struct AtomicF64 {
bits: AtomicU64,
}
impl AtomicF64 {
#[inline]
#[must_use]
pub const fn new(value: f64) -> Self {
Self {
bits: AtomicU64::new(value.to_bits()),
}
}
#[inline]
#[must_use]
pub fn load(&self, order: Ordering) -> f64 {
f64::from_bits(self.bits.load(order))
}
#[inline]
pub fn store(&self, value: f64, order: Ordering) {
self.bits.store(value.to_bits(), order);
}
}
pub struct OncePtr<T> {
inner: PyAtomic<*mut T>,
}
+580 -219
View File
@@ -66,7 +66,6 @@ struct ImplContext {
attribute_items: ItemNursery,
method_items: MethodNursery,
getset_items: GetSetNursery,
member_items: MemberNursery,
extend_slots_items: ItemNursery,
class_extensions: Vec<TokenStream>,
errors: Vec<syn::Error>,
@@ -99,7 +98,6 @@ fn extract_items_into_context<'a, Item>(
}
context.errors.ok_or_push(context.method_items.validate());
context.errors.ok_or_push(context.getset_items.validate());
context.errors.ok_or_push(context.member_items.validate());
}
pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Result<TokenStream> {
@@ -109,7 +107,6 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
if imp.generics.params.is_empty() {
context.self_ty_subst = Some((*imp.self_ty).clone());
context.getset_items.class_ty = context.self_ty_subst.clone();
context.member_items.class_ty = context.self_ty_subst.clone();
}
extract_items_into_context(&mut context, imp.items.iter_mut());
@@ -182,7 +179,6 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
let payload_ty = attr_payload.unwrap_or(payload_guess);
let method_def = &context.method_items;
let getset_impl = &context.getset_items;
let member_impl = &context.member_items;
let extend_impl = context.attribute_items.validate()?;
let slots_impl = context.extend_slots_items.validate()?;
let class_extensions = &context.class_extensions;
@@ -197,7 +193,6 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
class: &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType>,
) {
#getset_impl
#member_impl
#extend_impl
#(#class_extensions)*
}
@@ -295,7 +290,6 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
let method_def = &context.method_items;
let getset_impl = &context.getset_items;
let member_impl = &context.member_items;
let extend_impl = &context.attribute_items.validate()?;
let slots_impl = &context.extend_slots_items.validate()?;
let class_extensions = &context.class_extensions;
@@ -316,7 +310,6 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
class: &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType>,
) {
#getset_impl
#member_impl
#extend_impl
#with_impl
#(#class_extensions)*
@@ -433,15 +426,15 @@ fn class_def_ty(self_ty: Option<&syn::Type>) -> Option<TokenStream> {
Some(quote!(#ty))
}
/// Const `Option<&'static str>`: table entry, else the Rust doc.
/// Generic impls cannot name `Self` from a nested const, so they keep the Rust doc.
/// Const `Option<&'static str>`. A non-empty Rust doc is the docstring.
/// The attribute table is used only when there is no Rust doc. Generic impls
/// cannot name `Self` from a nested const, so without a Rust doc they store none.
fn attr_doc_expr(self_ty: Option<&syn::Type>, attr: &str, rust_doc: Option<String>) -> TokenStream {
let fallback = match rust_doc {
Some(doc) => quote!(Some(#doc)),
None => quote!(None),
};
if let Some(doc) = rust_doc.filter(|doc| !doc.is_empty()) {
return quote!(Some(#doc));
}
let Some(ty) = class_def_ty(self_ty) else {
return fallback;
return quote!(None);
};
quote! {
{
@@ -449,11 +442,10 @@ fn attr_doc_expr(self_ty: Option<&syn::Type>, attr: &str, rust_doc: Option<Strin
<#ty as ::rustpython_vm::class::PyClassDef>::ATTR_DOCS,
#attr,
);
const RUST_DOC: Option<&str> = #fallback;
if let Some(doc) = FOUND {
if doc.is_empty() { None } else { Some(doc) }
} else {
RUST_DOC
None
}
}
}
@@ -482,6 +474,17 @@ fn type_matches_path(ty: &syn::Type, path: &syn::Path) -> bool {
type_last.ident == path_last.ident
}
struct MemberTableTokens<'a> {
members: &'a TokenStream,
parts: &'a TokenStream,
checks: &'a TokenStream,
}
struct ClassDefExtras<'a> {
attrs: &'a [Attribute],
member_table: MemberTableTokens<'a>,
}
fn generate_class_def(
ident: &Ident,
name: &str,
@@ -489,14 +492,16 @@ fn generate_class_def(
base: Option<syn::Path>,
metaclass: Option<String>,
unhashable: bool,
attrs: &[Attribute],
extras: ClassDefExtras<'_>,
) -> Result<TokenStream> {
let attrs = extras.attrs;
let module_key = module_name.unwrap_or("builtins");
let attr_docs = crate::class_docs::attr_docs_tokens(module_name, name);
let doc = rustpython_doc::get_qualified(module_key, name, None, true)
.filter(|doc| !doc.is_empty())
.map(str::to_owned)
.or_else(|| attrs.doc());
let doc = attrs.doc().filter(|doc| !doc.is_empty()).or_else(|| {
rustpython_doc::get_qualified(module_key, name, None, true)
.filter(|doc| !doc.is_empty())
.map(str::to_owned)
});
let doc = if let Some(doc) = doc {
quote!(Some(#doc))
} else {
@@ -600,6 +605,12 @@ fn generate_class_def(
None
};
let MemberTableTokens {
members,
parts: member_parts_impl,
checks: member_checks,
} = extras.member_table;
let tokens = quote! {
impl ::rustpython_vm::class::PyClassDef for #ident {
const NAME: &'static str = #name;
@@ -609,10 +620,17 @@ fn generate_class_def(
const ATTR_DOCS: &'static [(&'static str, &'static str)] = #attr_docs;
const BASICSIZE: usize = #basicsize;
const UNHASHABLE: bool = #unhashable;
const MEMBERS: &'static [::rustpython_vm::builtins::descriptor::PyMemberSpec] = #members;
type Base = #base_or_object;
fn assert_member_layout() {
#member_checks
}
}
#member_parts_impl
impl ::rustpython_vm::class::StaticType for #ident {
fn static_cell() -> &'static ::rustpython_vm::common::static_cell::StaticCell<::rustpython_vm::builtins::PyTypeRef> {
::rustpython_vm::common::static_cell! {
@@ -667,11 +685,12 @@ pub(crate) fn impl_pyclass(attr: PunctuatedNestedMeta, item: Item) -> Result<Tok
None
};
let item = if base.is_some() {
let mut item = if base.is_some() {
ensure_repr_c(item)
} else {
item
};
let (members, member_parts, member_checks) = extract_py_members(&mut item)?;
let (ident, attrs) = pyclass_ident_and_attrs(&item)?;
@@ -697,7 +716,14 @@ pub(crate) fn impl_pyclass(attr: PunctuatedNestedMeta, item: Item) -> Result<Tok
base.clone(),
metaclass,
unhashable,
attrs,
ClassDefExtras {
attrs,
member_table: MemberTableTokens {
members: &members,
parts: &member_parts,
checks: &member_checks,
},
},
)?;
const ALLOWED_TRAVERSE_OPTS: &[&str] = &["manual"];
@@ -1376,44 +1402,10 @@ where
Item: ItemLike + ToTokens + GetIdent,
{
fn gen_impl_item(&self, args: ImplItemArgs<'_, Item>) -> Result<()> {
let func = args
.item
.function_or_method()
.map_err(|_| self.new_syn_error(args.item.span(), "can only be on a method"))?;
let ident = &func.sig().ident;
let item_attr = args.attrs.remove(self.index());
let item_meta = MemberItemMeta::from_attr(ident.clone(), &item_attr)?;
let (py_name, member_item_kind) = item_meta.member_name()?;
let member_kind = item_meta.member_kind()?;
if let Some(ref s) = member_kind {
match s.as_str() {
"bool" | "object" => {}
other => {
return Err(self.new_syn_error(
args.item.span(),
&format!("unknown member type '{other}'"),
));
}
}
}
// Add #[allow(non_snake_case)] for setter methods
if matches!(member_item_kind, MemberItemKind::Set) {
let allow_attr: Attribute = parse_quote!(#[allow(non_snake_case)]);
args.attrs.push(allow_attr);
}
let doc = args.attrs.doc();
args.context.member_items.add_item(
&py_name,
member_item_kind,
member_kind,
ident.clone(),
doc,
)?;
Ok(())
Err(self.new_syn_error(
args.item.span(),
"belongs on a payload field, not in an impl. Put #[pymember(...)] on the struct field",
))
}
}
@@ -1597,7 +1589,7 @@ impl ToTokens for GetSetNursery {
#( #cfgs )*
{
const DOC: Option<&str> = #doc;
let mut getset = ::rustpython_vm::builtins::PyGetSet::new(#name.into(), class)
let mut getset = ::rustpython_vm::builtins::PyGetSet::new(#name, class, ctx)
.with_get(Self::#getter)
#setter;
if let Some(doc) = DOC {
@@ -1619,124 +1611,6 @@ impl ToTokens for GetSetNursery {
}
}
/// Member type as string, matching `Py_T_*` codes.
/// None means `MemberKind::ObjectEx`. Valid values: "bool", "object".
type MemberKindStr = Option<String>;
#[derive(Default)]
struct MemberNursery {
map: HashMap<String, MemberNurseryEntry>,
validated: bool,
class_ty: Option<syn::Type>,
}
struct MemberNurseryEntry {
kind: MemberKindStr,
getter: Option<Ident>,
setter: Option<Ident>,
doc: Option<String>,
}
#[derive(Clone, Copy, Eq, PartialEq)]
enum MemberItemKind {
Get,
Set,
}
impl MemberNursery {
fn add_item(
&mut self,
name: &str,
kind: MemberItemKind,
member_kind: MemberKindStr,
item_ident: Ident,
doc: Option<String>,
) -> Result<()> {
assert!(!self.validated, "new item is not allowed after validation");
let entry = self
.map
.entry(name.to_string())
.or_insert_with(|| MemberNurseryEntry {
kind: member_kind,
getter: None,
setter: None,
doc: None,
});
let func = match kind {
MemberItemKind::Get => &mut entry.getter,
MemberItemKind::Set => &mut entry.setter,
};
if func.is_some() {
bail_span!(item_ident, "Multiple member accessors with name '{}'", name);
}
*func = Some(item_ident);
if matches!(kind, MemberItemKind::Get)
&& let Some(doc) = doc
{
entry.doc = Some(doc);
}
Ok(())
}
fn validate(&mut self) -> Result<()> {
let mut errors = Vec::new();
#[expect(
clippy::iter_over_hash_type,
reason = "Iteration order doesn't matter here"
)]
for (name, entry) in &self.map {
if entry.getter.is_none() {
errors.push(err_span!(
entry.setter.as_ref().unwrap(),
"Member '{}' is missing a getter",
name
));
};
}
errors.into_result()?;
self.validated = true;
Ok(())
}
}
impl ToTokens for MemberNursery {
fn to_tokens(&self, tokens: &mut TokenStream) {
assert!(self.validated, "Call `validate()` before token generation");
let properties = self.map.iter().map(|(name, entry)| {
let setter = match &entry.setter {
Some(setter) => quote_spanned! { setter.span() => Some(Self::#setter)},
None => quote! { None },
};
let member_kind = match entry.kind.as_deref() {
Some("bool") => {
quote!(::rustpython_vm::builtins::descriptor::MemberKind::Bool)
}
Some("object") => {
quote!(::rustpython_vm::builtins::descriptor::MemberKind::Object)
}
_ => {
quote!(::rustpython_vm::builtins::descriptor::MemberKind::ObjectEx)
}
};
let getter = entry.getter.as_ref().unwrap();
let doc = attr_doc_expr(self.class_ty.as_ref(), name, entry.doc.clone());
quote_spanned! { getter.span() =>
{
const DOC: Option<&str> = #doc;
class.set_str_attr(
#name,
ctx.new_member(#name, #member_kind, Self::#getter, #setter, class, DOC),
ctx,
);
}
}
});
tokens.extend(properties);
}
}
struct MethodItemMeta(ItemMetaInner);
impl ItemMeta for MethodItemMeta {
@@ -1918,7 +1792,15 @@ impl SlotItemMeta {
struct MemberItemMeta(ItemMetaInner);
impl ItemMeta for MemberItemMeta {
const ALLOWED_NAMES: &'static [&'static str] = &["type", "setter"];
const ALLOWED_NAMES: &'static [&'static str] = &[
"type",
"writable",
"audit_read",
"name",
"path",
"doc",
"offset",
];
fn from_inner(inner: ItemMetaInner) -> Self {
Self(inner)
@@ -1930,48 +1812,527 @@ impl ItemMeta for MemberItemMeta {
}
impl MemberItemMeta {
fn member_name(&self) -> Result<(String, MemberItemKind)> {
let inner = self.inner();
let sig_name = inner.item_name();
let extract_prefix_name = |prefix, item_typ| {
if let Some(name) = sig_name.strip_prefix(prefix) {
if name.is_empty() {
Err(err_span!(
inner.meta_ident,
r#"A #[{}({typ})] fn with a {prefix}* name must \
have something after "{prefix}""#,
inner.meta_name(),
typ = item_typ,
prefix = prefix
))
} else {
Ok(name.to_owned())
}
} else {
Err(err_span!(
inner.meta_ident,
r#"A #[{}(setter)] fn must either have a `name` \
parameter or a fn name along the lines of "set_*""#,
inner.meta_name()
))
}
};
let kind = if inner._bool("setter")? {
MemberItemKind::Set
} else {
MemberItemKind::Get
};
let name = match kind {
MemberItemKind::Get => sig_name,
MemberItemKind::Set => extract_prefix_name("set_", "setter")?,
};
Ok((name, kind))
fn explicit_name(&self) -> Result<Option<String>> {
self.inner()._optional_str("name")
}
fn member_kind(&self) -> Result<Option<String>> {
let inner = self.inner();
inner._optional_str("type")
let kind = self.inner()._optional_str("type")?;
if let Some(value) = &kind {
let span = self
.inner()
.meta_map
.get("type")
.map_or_else(|| self.inner().meta_ident.span(), |(_, meta)| meta.span());
match value.as_str() {
"object_ex" => {}
"object" | "bool" | "double" | "int" | "uint" | "py_ssize_t" => {
return Err(syn::Error::new(
span,
format!(
"member kind is inferred from the field type; remove `type = \"{value}\"`"
),
));
}
other => {
return Err(syn::Error::new(
span,
format!("unknown member type '{other}'"),
));
}
}
}
Ok(kind)
}
fn path_tokens(&self) -> Result<Option<TokenStream>> {
let inner = self.inner();
let Some(value) = inner._optional_str("path")? else {
return Ok(None);
};
let span = inner
.meta_map
.get("path")
.map_or_else(|| inner.meta_ident.span(), |(_, meta)| meta.span());
parse_member_subpath(&value, span).map(Some)
}
fn writable(&self) -> Result<bool> {
self.inner()._bool("writable")
}
fn audit_read(&self) -> Result<bool> {
self.inner()._bool("audit_read")
}
fn offset_tokens(&self) -> Result<Option<TokenStream>> {
let Some((_, meta)) = self.inner().meta_map.get("offset") else {
return Ok(None);
};
let Meta::NameValue(name_value) = meta else {
return Err(syn::Error::new(
meta.span(),
"#[pymember(offset = ...)] must be an expression",
));
};
Ok(Some(name_value.value.to_token_stream()))
}
/// `doc = false` suppresses the stored docstring. The member docstring is
/// the field's `///` comment (or the `///` lines immediately above a
/// struct-level `#[pymember]`). A string `doc` is rejected.
fn suppress_doc(&self) -> Result<bool> {
let Some((_, meta)) = self.inner().meta_map.get("doc") else {
return Ok(false);
};
match meta {
Meta::NameValue(syn::MetaNameValue {
value:
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Bool(lit),
..
}),
..
}) if !lit.value => Ok(true),
_ => Err(syn::Error::new(
meta.span(),
"#[pymember] docstring is the `///` comment; only `doc = false` is accepted",
)),
}
}
}
fn parse_member_subpath(value: &str, span: Span) -> Result<TokenStream> {
if value.is_empty() {
return Err(syn::Error::new(
span,
"#[pymember(path = ...)] must name a subfield",
));
}
let stream: TokenStream = value.parse().map_err(|err| syn::Error::new(span, err))?;
let trees: Vec<TokenTree> = stream.into_iter().collect();
if trees.is_empty() {
return Err(syn::Error::new(
span,
"#[pymember(path = ...)] must name a subfield",
));
}
let mut expect_ident = true;
for tree in &trees {
match tree {
TokenTree::Ident(_) if expect_ident => expect_ident = false,
TokenTree::Punct(punct) if !expect_ident && punct.as_char() == '.' => {
expect_ident = true;
}
_ => {
return Err(syn::Error::new(
span,
"#[pymember(path = ...)] must be a field path",
));
}
}
}
if expect_ident {
return Err(syn::Error::new(
span,
"#[pymember(path = ...)] must be a field path",
));
}
Ok(trees.into_iter().collect())
}
struct BuiltMember {
name: String,
cfgs: Vec<Attribute>,
kind: TokenStream,
offset: TokenStream,
flags: TokenStream,
doc: TokenStream,
check: TokenStream,
span: Span,
}
fn is_doc_attr(attr: &Attribute) -> bool {
attr.path().is_ident("doc")
}
fn doc_attr_line(attr: &Attribute) -> Option<String> {
let syn::Meta::NameValue(name_value) = &attr.meta else {
return None;
};
let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(lit),
..
}) = &name_value.value
else {
return None;
};
Some(lit.value().trim().to_owned())
}
/// `#[pymember]` on a struct has no field. `///` lines placed immediately
/// above that attribute (after any other attribute) are its docstring and are
/// removed so they are not also the class docstring. Several struct-level
/// members each take the `///` lines directly above them.
fn split_struct_pymembers(attrs: &mut Vec<Attribute>) -> Vec<(Attribute, Option<String>)> {
let mut kept = Vec::with_capacity(attrs.len());
let mut members = Vec::new();
let mut pending_docs = Vec::new();
let mut pending_doc_attrs = Vec::new();
for attr in attrs.drain(..) {
if is_doc_attr(&attr)
&& let Some(text) = doc_attr_line(&attr)
{
pending_docs.push(text);
pending_doc_attrs.push(attr);
continue;
}
if attr.path().is_ident("pymember") {
let doc = if pending_docs.is_empty() {
None
} else {
Some(pending_docs.join("\n")).filter(|doc| !doc.is_empty())
};
pending_docs.clear();
pending_doc_attrs.clear();
members.push((attr, doc));
continue;
}
kept.append(&mut pending_doc_attrs);
pending_docs.clear();
kept.push(attr);
}
kept.append(&mut pending_doc_attrs);
*attrs = kept;
members
}
fn split_pymember_attrs(attrs: &mut Vec<Attribute>) -> Vec<Attribute> {
let mut kept = Vec::with_capacity(attrs.len());
let mut members = Vec::new();
for attr in attrs.drain(..) {
if attr.path().is_ident("pymember") {
members.push(attr);
} else {
kept.push(attr);
}
}
*attrs = kept;
members
}
fn field_cfg_attrs(attrs: &[Attribute]) -> Result<Vec<Attribute>> {
let mut cfgs = Vec::new();
for attr in attrs {
if attr.path().is_ident("cfg") {
if !matches!(attr.meta, syn::Meta::List(_)) {
return Err(syn::Error::new(
attr.span(),
"#[cfg] on a #[pymember] field must have a predicate",
));
}
cfgs.push(attr.clone());
}
}
Ok(cfgs)
}
fn cfg_not_all(cfgs: &[Attribute]) -> TokenStream {
let preds = cfgs.iter().map(|attr| match &attr.meta {
syn::Meta::List(list) => list.tokens.clone(),
_ => quote!(all()),
});
quote!(#[cfg(not(all(#(#preds),*)))] )
}
fn member_kind_tokens(
kind: Option<&str>,
field_tokens: Option<&TokenStream>,
span: Span,
) -> Result<TokenStream> {
let Some(field_tokens) = field_tokens else {
let name = match kind {
None => "Object",
Some("object_ex") => "ObjectEx",
Some(other) => {
return Err(syn::Error::new(
span,
format!("unknown member type '{other}'"),
));
}
};
let ident = Ident::new(name, span);
return Ok(quote!(::rustpython_vm::builtins::descriptor::MemberKind::#ident));
};
let inferred = quote_spanned! { span =>
::rustpython_vm::builtins::descriptor::member_kind_of(
|payload: &Self| &payload.#field_tokens,
)
};
let kind = match kind {
None => inferred,
Some("object_ex") => quote_spanned! { span =>
{
let inferred = #inferred;
::core::assert!(
matches!(
inferred,
::rustpython_vm::builtins::descriptor::MemberKind::Object
),
"`type = \"object_ex\"` requires an object field",
);
::rustpython_vm::builtins::descriptor::MemberKind::ObjectEx
}
},
Some(other) => {
return Err(syn::Error::new(
span,
format!("unknown member type '{other}'"),
));
}
};
Ok(kind)
}
fn member_layout_tokens(readonly: bool) -> TokenStream {
let name = if readonly {
"MemberLayout"
} else {
"MemberCell"
};
let ident = Ident::new(name, Span::call_site());
quote!(::rustpython_vm::builtins::descriptor::#ident)
}
fn member_flag_tokens(readonly: bool, audit_read: bool) -> TokenStream {
let mut flag_parts = Vec::new();
if readonly {
flag_parts.push(quote!(::rustpython_vm::builtins::descriptor::PY_READONLY));
}
if audit_read {
flag_parts.push(quote!(::rustpython_vm::builtins::descriptor::PY_AUDIT_READ));
}
if flag_parts.is_empty() {
quote!(0)
} else {
quote!(#(#flag_parts)|*)
}
}
fn build_member(
attr: &Attribute,
field: Option<(&syn::Field, usize)>,
cfgs: &[Attribute],
class_ty: &syn::Type,
rust_doc: Option<String>,
seen: &mut Vec<(String, Vec<Attribute>)>,
) -> Result<BuiltMember> {
let fallback_ident = field
.and_then(|(field, _)| field.ident.clone())
.unwrap_or_else(|| Ident::new("member", attr.span()));
let meta = MemberItemMeta::from_attr(fallback_ident, attr)?;
let explicit_name = meta.explicit_name()?;
let name = if let Some(name) = explicit_name {
name
} else if let Some((field, _)) = field {
field
.ident
.as_ref()
.map(|ident| ident.to_string())
.ok_or_else(|| {
syn::Error::new(
attr.span(),
"#[pymember] on an unnamed field requires `name`",
)
})?
} else {
return Err(syn::Error::new(
attr.span(),
"#[pymember] on a struct requires `name`",
));
};
if seen
.iter()
.any(|(seen_name, seen_cfgs)| seen_name == &name && seen_cfgs == cfgs)
{
return Err(syn::Error::new(
attr.span(),
format!("Multiple members with name '{name}'"),
));
}
seen.push((name.clone(), cfgs.to_vec()));
let kind = meta.member_kind()?;
let path = meta.path_tokens()?;
let readonly = !meta.writable()?;
let audit_read = meta.audit_read()?;
let offset_expr = meta.offset_tokens()?;
if field.is_some() && offset_expr.is_some() {
return Err(syn::Error::new(
attr.span(),
"#[pymember(offset = ...)] is only valid on a struct, not on a field",
));
}
if field.is_none() && path.is_some() {
return Err(syn::Error::new(
attr.span(),
"#[pymember(path = ...)] is only valid on a field",
));
}
let (offset, check, kind_tokens) = if let Some((field, index)) = field {
let index_tokens = match &field.ident {
Some(ident) => ident.to_token_stream(),
None => syn::Index::from(index).into_token_stream(),
};
let field_tokens = if let Some(path) = &path {
quote!(#index_tokens.#path)
} else {
index_tokens
};
let offset = quote_spanned! { attr.span() =>
::rustpython_vm::object::payload_offset::<Self>() as isize
+ ::core::mem::offset_of!(Self, #field_tokens) as isize
};
let layout = member_layout_tokens(readonly);
let check = quote_spanned! { attr.span() =>
let _ = |payload: *const Self| {
fn assert_member_field<T: #layout>(_: *const T) {}
// SAFETY: `payload` is not dereferenced. `addr_of`
// only names the field so its type can be checked.
let field = unsafe { core::ptr::addr_of!((*payload).#field_tokens) };
assert_member_field(field);
};
};
let kind_tokens = member_kind_tokens(kind.as_deref(), Some(&field_tokens), attr.span())?;
(offset, check, kind_tokens)
} else {
let Some(offset_expr) = offset_expr else {
return Err(syn::Error::new(
attr.span(),
"#[pymember] on a struct requires `offset`",
));
};
let kind_tokens = member_kind_tokens(kind.as_deref(), None, attr.span())?;
(offset_expr, quote!(), kind_tokens)
};
let doc = if meta.suppress_doc()? {
quote!(None)
} else {
attr_doc_expr(Some(class_ty), &name, rust_doc)
};
Ok(BuiltMember {
name,
cfgs: cfgs.to_vec(),
kind: kind_tokens,
offset,
flags: member_flag_tokens(readonly, audit_read),
doc,
check,
span: attr.span(),
})
}
fn emit_member_table(
ident: &Ident,
members: &[BuiltMember],
) -> (TokenStream, TokenStream, TokenStream) {
if members.is_empty() {
return (quote!(&[]), quote!(), quote!());
}
let mut parts = TokenStream::new();
let mut checks = TokenStream::new();
let mut names = Vec::new();
for (index, member) in members.iter().enumerate() {
let const_name = Ident::new(&format!("__PYMEMBER_{index}"), member.span);
let cfgs = &member.cfgs;
let not_cfg = cfg_not_all(cfgs);
let kind = &member.kind;
let offset = &member.offset;
let flags = &member.flags;
let doc = &member.doc;
let name = &member.name;
let check = &member.check;
parts.extend(quote_spanned! { member.span =>
#(#cfgs)*
const #const_name: &'static [::rustpython_vm::builtins::descriptor::PyMemberSpec] = &[
::rustpython_vm::builtins::descriptor::PyMemberSpec {
name: #name,
kind: #kind,
offset: #offset,
flags: #flags,
doc: #doc,
},
];
#not_cfg
const #const_name: &'static [::rustpython_vm::builtins::descriptor::PyMemberSpec] = &[];
});
if !check.is_empty() {
checks.extend(quote_spanned! { member.span =>
#(#cfgs)*
#check
});
}
names.push(const_name);
}
let len_terms = names.iter().map(|name| quote!(#ident::#name.len()));
let members_expr = quote! {
&::rustpython_vm::builtins::descriptor::PyMemberSpec::concat::<{ #(#len_terms)+* }>(
&[#(#ident::#names),*],
)
};
let parts_impl = quote! {
impl #ident {
#parts
}
};
(members_expr, parts_impl, checks)
}
fn extract_py_members(item: &mut Item) -> Result<(TokenStream, TokenStream, TokenStream)> {
let Item::Struct(item_struct) = item else {
return Ok((quote!(&[]), quote!(), quote!()));
};
let struct_ident = item_struct.ident.clone();
let class_ty: syn::Type = syn::parse_quote!(#struct_ident);
let mut seen = Vec::new();
let mut built = Vec::new();
for (attr, rust_doc) in split_struct_pymembers(&mut item_struct.attrs) {
built.push(build_member(
&attr,
None,
&[],
&class_ty,
rust_doc,
&mut seen,
)?);
}
let field_count = match &item_struct.fields {
syn::Fields::Named(fields) => fields.named.len(),
syn::Fields::Unnamed(fields) => fields.unnamed.len(),
syn::Fields::Unit => 0,
};
for index in 0..field_count {
let field = match &mut item_struct.fields {
syn::Fields::Named(fields) => &mut fields.named[index],
syn::Fields::Unnamed(fields) => &mut fields.unnamed[index],
syn::Fields::Unit => unreachable!(),
};
let cfgs = field_cfg_attrs(&field.attrs)?;
// Every #[pymember] on the field shares that field's `///` comment.
let rust_doc = field.attrs.doc().filter(|doc| !doc.is_empty());
let attrs = split_pymember_attrs(&mut field.attrs);
for attr in &attrs {
built.push(build_member(
attr,
Some((field, index)),
&cfgs,
&class_ty,
rust_doc.clone(),
&mut seen,
)?);
}
}
Ok(emit_member_table(&struct_ident, &built))
}
struct ExtractedImplAttrs {
+18 -17
View File
@@ -255,10 +255,11 @@ pub(crate) fn impl_pymodule(args: PyModuleArgs, module_item: Item) -> Result<Tok
let module_name = context.name.as_str();
let function_items = context.function_items.validate()?;
let attribute_items = context.attribute_items.validate()?;
let doc = rustpython_doc::get(module_name)
.filter(|doc| !doc.is_empty())
.map(str::to_owned)
.or(doc);
let doc = doc.filter(|doc| !doc.is_empty()).or_else(|| {
rustpython_doc::get(module_name)
.filter(|doc| !doc.is_empty())
.map(str::to_owned)
});
let doc = if let Some(doc) = doc {
quote!(Some(#doc))
} else {
@@ -708,22 +709,22 @@ impl ModuleItem for FunctionItem {
}
let module = args.module_name();
let rust_doc = args.attrs.doc();
// TODO: doc must exist at least one of code or CPython
let rust_doc = args.attrs.doc().filter(|doc| !doc.is_empty());
let docs = py_names
.iter()
.map(|py_name| {
let doc = rustpython_doc::get_qualified(module, py_name, None, false)
.filter(|doc| !doc.is_empty())
.map(str::to_owned)
.or_else(|| {
if args.context.is_sub {
unique_submodule_func_doc(py_name)
} else {
None
}
})
.or_else(|| rust_doc.clone());
let doc = rust_doc.clone().or_else(|| {
rustpython_doc::get_qualified(module, py_name, None, false)
.filter(|doc| !doc.is_empty())
.map(str::to_owned)
.or_else(|| {
if args.context.is_sub {
unique_submodule_func_doc(py_name)
} else {
None
}
})
});
let doc = match doc {
Some(doc) => quote!(Some(#doc)),
None => quote!(None),
+8 -3
View File
@@ -2,7 +2,7 @@ use crate::util::{ItemMeta, ItemMetaInner};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{DeriveInput, Ident, Item, Result};
use syn_ext::ext::{AttributeExt, GetIdent};
use syn_ext::ext::{AttributeExt, AttributeIteratorExt, GetIdent};
use syn_ext::types::{Meta, PunctuatedNestedMeta};
// #[pystruct_sequence_data] - For Data structs
@@ -524,13 +524,18 @@ pub(crate) fn impl_pystruct_sequence(
} else {
class_name.clone()
};
let doc = rustpython_doc::get(module_class_name.as_str()).or_else(|| {
let db_doc = rustpython_doc::get(module_class_name.as_str()).or_else(|| {
// os re-exports the posix/nt struct sequences.
let class_name = module_class_name.strip_prefix("os.")?;
rustpython_doc::get(&format!("posix.{class_name}"))
.or_else(|| rustpython_doc::get(&format!("nt.{class_name}")))
});
let doc = doc.filter(|doc| !doc.is_empty());
let db_doc = db_doc.filter(|doc| !doc.is_empty()).map(str::to_owned);
let doc = struct_item
.attrs
.doc()
.filter(|doc| !doc.is_empty())
.or(db_doc);
let doc = match doc {
Some(doc) => quote!(Some(#doc)),
None => quote!(None),
+3 -2
View File
@@ -867,8 +867,9 @@ fn args_const(pieces: &[SigPiece]) -> TokenStream {
/// Expression of type `Option<&'static str>`: the internal doc, or the plain
/// doc when the arguments cannot form a signature.
/// `doc` is a const `Option<&'static str>`. A table entry wins; an empty
/// string is no docstring. The text is composed into the internal doc so
/// `doc` is a const `Option<&'static str>` already chosen by the caller
/// (a Rust doc comment wins over the stored docstring). An empty string is
/// no docstring. The text is composed into the internal doc so
/// `__text_signature__` stays on the signature half.
pub(crate) fn internal_doc_tokens(
sig: &Signature,
+48 -1
View File
@@ -142,6 +142,11 @@ pub fn derive_from_args(input: TokenStream) -> TokenStream {
/// ...
/// }
/// ```
/// ## Docstrings
/// A `///` doc comment on the struct, or on a `#[pymethod]` / `#[pyclassmethod]` /
/// `#[pystaticmethod]` / `#[pygetset]` / `#[pymember]` item, is the Python
/// docstring and overrides the stored documentation. The stored documentation
/// is used only when that item has no `///` comment. Use `//` for developer notes.
/// ## Inner markers
/// ### pymethod/pyclassmethod/pystaticmethod
/// `pymethod` is used to mark a method of the Python class.
@@ -210,6 +215,41 @@ pub fn derive_from_args(input: TokenStream) -> TokenStream {
/// }
/// ```
/// ### pymember
/// Declares an offset member on a payload field. The struct `#[pyclass]` builds
/// a `PyClassDef::MEMBERS` table and `extend_class` registers one
/// `member_descriptor` per entry. The member kind is inferred from the field
/// type: `bool` / `AtomicBool`, `i32` / `AtomicI32`, `u32` / `AtomicU32`,
/// `isize` / `AtomicIsize`, `f64` / `AtomicF64`, or an object pointer
/// (`PyObjectRef`, `PyRef<T>`, `Option` of those, `PyAtomicRef<PyObject>`,
/// `PyAtomicRef<Option<PyObject>>`, `PyAtomicRef<Option<T>>`,
/// `&'static Py<T>`, `&'static PyStrInterned`).
///
/// - `type`: only `"object_ex"`, on an object field or a struct-level offset
/// member. `"object"`, `"bool"`, `"int"`, `"uint"`, `"double"`, and
/// `"py_ssize_t"` are rejected because those kinds are inferred.
/// - `writable`: accept stores. Members are readonly without it. A writable
/// object member must be `PyAtomicRef<PyObject>` (never null) or
/// `PyAtomicRef<Option<PyObject>>` (nullable), a writable bool must be
/// `AtomicBool`, a writable int must be `AtomicI32`, a writable uint must be
/// `AtomicU32`, a writable double must be `AtomicF64`, and a writable
/// py_ssize_t must be `AtomicIsize`.
/// - `audit_read`: audit `object.__getattr__` before the load.
/// - `name`: Python attribute name. Defaults to the field name.
/// - `path`: subfield of the annotated field (`value` with `path = "re"`).
/// - `doc`: only `doc = false`, which stores no docstring. Otherwise the
/// docstring is the field's `///` comment when present, and the stored
/// attribute documentation when the field has no `///` comment. Every
/// `#[pymember]` on one field shares that comment. A string `doc` is rejected.
///
/// A struct-level `#[pymember]` (after `#[pyclass]`) has no field. `offset` is
/// required there and rejected on a field. `///` lines placed immediately above
/// that attribute are its docstring; otherwise the stored attribute
/// documentation is used. One field may carry several `#[pymember]` attributes.
/// A `#[cfg]` on the field gates that entry.
/// ```rust, ignore
/// #[pymember(name = "fget")]
/// getter: PyAtomicRef<Option<PyObject>>,
/// ```
/// # Trait
/// `#[pyclass]` on traits functions a lot like `#[pyclass]` on `impl` blocks.
/// Note that associated functions that are annotated with `#[pymethod]` or similar **must**
@@ -223,6 +263,8 @@ pub fn pyclass(attr: TokenStream, item: TokenStream) -> TokenStream {
/// Helper macro to define `Exception` types.
/// More-or-less is an alias to `pyclass` macro.
/// A `///` comment on the exception type is its docstring and overrides the
/// stored documentation. Use `//` for developer notes.
///
/// This macro serves a goal of generating multiple
/// `BaseException` / `Exception`
@@ -240,6 +282,10 @@ pub fn pyexception(attr: TokenStream, item: TokenStream) -> TokenStream {
/// It defines a Python module in the form of a `module_def` function in the module;
/// this has to be used in a `add_native_module` to properly register the module.
/// Additionally, this macro defines 'MODULE_NAME' and 'DOC' in the module.
/// A `///` comment on the module is its docstring and overrides the stored
/// module documentation. `#[pyfunction]` works the same way: its `///` comment
/// is the function docstring and overrides the stored function documentation.
/// Use `//` for developer notes.
/// # Arguments
/// - `name`: the name of the python module,
/// by default, it is the name of the module, but this can be configured.
@@ -311,7 +357,8 @@ pub fn pymodule(attr: TokenStream, item: TokenStream) -> TokenStream {
/// Attribute macro for defining Python struct sequence types.
///
/// This macro is applied to an empty struct to create a Python type
/// that wraps a Data struct.
/// that wraps a Data struct. A `///` comment on that struct is the type's
/// docstring and overrides the stored documentation. Use `//` for developer notes.
///
/// # Example
/// ```ignore
-1
View File
@@ -111,7 +111,6 @@ pub(crate) mod _asyncio {
}
}
/// asyncio.Future implementation
#[pyattr]
#[pyclass(name = "Future", module = "_asyncio", traverse)]
#[derive(Debug, PyPayload)]
+7 -7
View File
@@ -1093,7 +1093,7 @@ mod _sqlite3 {
}
if let Some(cursor_ref) = cursor.downcast_ref::<Cursor>()
&& let Some(factory) = zelf.row_factory.to_owned()
&& let Some(factory) = zelf.row_factory.load_owned()
{
let _ = unsafe { cursor_ref.row_factory.swap(Some(factory)) };
}
@@ -1187,7 +1187,7 @@ mod _sqlite3 {
parameters: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyRef<Cursor>> {
let row_factory = zelf.row_factory.to_owned();
let row_factory = zelf.row_factory.load_owned();
let cursor = Cursor::new(zelf, row_factory, vm).into_ref(&vm.ctx);
Cursor::execute(cursor, sql, parameters, vm)
}
@@ -1199,7 +1199,7 @@ mod _sqlite3 {
seq_of_params: ArgIterable,
vm: &VirtualMachine,
) -> PyResult<PyRef<Cursor>> {
let row_factory = zelf.row_factory.to_owned();
let row_factory = zelf.row_factory.load_owned();
let cursor = Cursor::new(zelf, row_factory, vm).into_ref(&vm.ctx);
Cursor::executemany(cursor, sql, seq_of_params, vm)
}
@@ -1210,7 +1210,7 @@ mod _sqlite3 {
script: PyUtf8StrRef,
vm: &VirtualMachine,
) -> PyResult<PyRef<Cursor>> {
let row_factory = zelf.row_factory.to_owned();
let row_factory = zelf.row_factory.load_owned();
Cursor::executescript(
Cursor::new(zelf, row_factory, vm).into_ref(&vm.ctx),
script,
@@ -1669,7 +1669,7 @@ mod _sqlite3 {
#[pygetset]
fn row_factory(&self) -> Option<PyObjectRef> {
self.row_factory.to_owned()
self.row_factory.load_owned()
}
#[pygetset(setter)]
fn set_row_factory(
@@ -2146,7 +2146,7 @@ mod _sqlite3 {
#[pygetset]
fn row_factory(&self) -> Option<PyObjectRef> {
self.row_factory.to_owned()
self.row_factory.load_owned()
}
#[pygetset(setter)]
@@ -2346,7 +2346,7 @@ mod _sqlite3 {
let row = vm.ctx.new_tuple(row);
if let Some(row_factory) = zelf.row_factory.to_owned() {
if let Some(row_factory) = zelf.row_factory.load_owned() {
row_factory
.call((zelf.to_owned(), row), vm)
.map(PyIterReturn::Return)
+2 -11
View File
@@ -496,7 +496,6 @@ mod _multiprocessing {
self.name.clone()
}
/// Acquire the semaphore/lock.
// _multiprocessing_SemLock_acquire_impl
#[pymethod]
fn acquire(&self, args: AcquireArgs, vm: &VirtualMachine) -> PyResult<bool> {
@@ -609,7 +608,6 @@ mod _multiprocessing {
.store(host_multiprocessing::current_thread_id(), Ordering::Release);
}
/// Release the semaphore/lock.
// _multiprocessing_SemLock_release_impl
#[pymethod]
fn release(&self, vm: &VirtualMachine) -> PyResult<()> {
@@ -670,7 +668,6 @@ mod _multiprocessing {
Ok(())
}
/// Enter the semaphore/lock (context manager).
// _multiprocessing_SemLock___enter___impl
#[pymethod(name = "__enter__")]
fn enter(&self, vm: &VirtualMachine) -> PyResult<bool> {
@@ -684,7 +681,6 @@ mod _multiprocessing {
)
}
/// Exit the semaphore/lock (context manager).
// _multiprocessing_SemLock___exit___impl
#[pymethod]
fn __exit__(&self, _args: ExitArgs, vm: &VirtualMachine) -> PyResult<()> {
@@ -719,7 +715,6 @@ mod _multiprocessing {
zelf.into_ref_with_type(vm, cls).map(Into::into)
}
/// Rezero the net acquisition count after fork().
// _multiprocessing_SemLock__after_fork_impl
#[pymethod]
fn _after_fork(&self) {
@@ -729,14 +724,13 @@ mod _multiprocessing {
self.last_tid.store(0, Ordering::Release);
}
/// SemLock objects cannot be pickled directly.
/// Use multiprocessing.synchronize.SemLock wrapper which handles pickling.
// SemLock objects cannot be pickled directly.
// Use multiprocessing.synchronize.SemLock wrapper which handles pickling.
#[pymethod]
fn __reduce__(&self, vm: &VirtualMachine) -> PyResult {
Err(vm.new_type_error("cannot pickle 'SemLock' object"))
}
/// Num of `acquire()`s minus num of `release()`s for this process.
// _multiprocessing_SemLock__count_impl
#[pymethod]
fn _count(&self) -> i32 {
@@ -744,14 +738,12 @@ mod _multiprocessing {
self.count.load(Ordering::Acquire)
}
/// Whether the lock is owned by this thread.
// _multiprocessing_SemLock__is_mine_impl
#[pymethod]
fn _is_mine(&self) -> bool {
ismine!(self)
}
/// Get the value of the semaphore.
// _multiprocessing_SemLock__get_value_impl
#[pymethod]
fn _get_value(&self, vm: &VirtualMachine) -> PyResult<i32> {
@@ -767,7 +759,6 @@ mod _multiprocessing {
}
}
/// Return whether semaphore has value zero.
// _multiprocessing_SemLock__is_zero_impl
#[pymethod]
fn _is_zero(&self, vm: &VirtualMachine) -> PyResult<bool> {
-3
View File
@@ -96,8 +96,6 @@ mod _pickle {
})
}
/// Return a memoryview of the raw memory underlying this buffer.
/// Will raise BufferError if the buffer isn't contiguous.
#[pymethod]
fn raw(&self, vm: &VirtualMachine) -> PyResult<PyRef<crate::vm::builtins::PyMemoryView>> {
let buffer = self.get(vm)?;
@@ -109,7 +107,6 @@ mod _pickle {
Ok(crate::vm::builtins::PyMemoryView::from_buffer(buffer, vm)?.into_ref(&vm.ctx))
}
/// Release the underlying buffer exposed by the PickleBuffer object.
#[pymethod]
fn release(&self) {
*self.buffer.write() = None;
+1 -1
View File
@@ -132,7 +132,7 @@ macro_rules! create_property {
macro_rules! create_readonly_int_property {
($ctx: expr, $attributes: expr, $name: expr, $class: expr, $element: ident) => {
let getset = crate::vm::builtins::PyGetSet::new($name, $class).with_get(
let getset = crate::vm::builtins::PyGetSet::new($name, $class, $ctx).with_get(
move |this: &Py<PyExpatLikeXmlParser>, vm: &VirtualMachine| -> PyObjectRef {
vm.ctx.new_int(*this.$element.read()).into()
},
+2 -2
View File
@@ -309,8 +309,8 @@ pub(crate) mod _struct {
Ok(self.ready(vm)?.format.clone())
}
/// The size an uninitialized `Struct` reports, which no format has
/// yet given a value.
// The size an uninitialized `Struct` reports, which no format has
// yet given a value.
#[pygetset]
fn size(&self) -> isize {
self.inner
+1 -1
View File
@@ -75,7 +75,7 @@ mod re {
}
}
/// Inner data for a match object.
// Inner data for a match object.
#[pyattr]
#[pyclass(module = "re", name = "Match", traverse)]
#[derive(PyPayload, Traverse)]
-6
View File
@@ -2155,9 +2155,6 @@ mod _socket {
.map_err(|e| e.into_pyexception(vm))
}
/// sendmsg_afalg([msg], *, op[, iv[, assoclen[, flags]]]) -> int
///
/// Set operation mode and target IV for an AF_ALG socket.
#[cfg(target_os = "linux")]
#[pymethod]
fn sendmsg_afalg(&self, args: SendmsgAfalgArgs, vm: &VirtualMachine) -> PyResult<usize> {
@@ -2192,9 +2189,6 @@ mod _socket {
.map_err(|e| e.into_pyexception(vm))
}
/// recvmsg(bufsize[, ancbufsize[, flags]]) -> (data, ancdata, msg_flags, address)
///
/// Receive normal data and ancillary data from the socket.
#[cfg(all(unix, not(target_os = "redox")))]
#[pymethod]
fn recvmsg(
+16 -18
View File
@@ -12,6 +12,7 @@ use crate::{
types::{Destructor, IterNext, Iterable, Representable, SelfIter},
};
use core::sync::atomic::{AtomicBool, Ordering};
use crossbeam_utils::atomic::AtomicCell;
fn warn_unawaited_asyncgen_method(ag: &Py<PyAsyncGen>, method: &str, vm: &VirtualMachine) {
@@ -26,7 +27,8 @@ fn warn_unawaited_asyncgen_method(ag: &Py<PyAsyncGen>, method: &str, vm: &Virtua
#[derive(Debug)]
pub struct PyAsyncGen {
inner: Coro,
running_async: AtomicCell<bool>,
#[pymember(name = "ag_running")]
running_async: AtomicBool,
// whether hooks have been initialized
ag_hooks_inited: AtomicCell<bool>,
// Distinct from the frame being finished: aclose() sets this
@@ -70,7 +72,7 @@ impl PyAsyncGen {
pub fn new(frame: FrameObjectRef, name: PyStrRef, qualname: PyStrRef) -> Self {
Self {
inner: Coro::new(frame, name, qualname),
running_async: AtomicCell::new(false),
running_async: AtomicBool::new(false),
ag_hooks_inited: AtomicCell::new(false),
ag_closed: AtomicCell::new(false),
ag_finalizer: PyMutex::new(None),
@@ -153,10 +155,6 @@ impl PyAsyncGen {
}
}
#[pygetset]
fn ag_running(&self, _vm: &VirtualMachine) -> bool {
self.running_async.load()
}
#[pygetset]
fn ag_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> {
self.inner.code()
}
@@ -289,12 +287,12 @@ impl PyAsyncGenWrappedValue {
ag.ag_closed.store(true);
}
if async_done {
ag.running_async.store(false);
ag.running_async.store(false, Ordering::Relaxed);
}
let val = val?.into_async_pyresult(vm)?;
match_class!(match val {
val @ Self => {
ag.running_async.store(false);
ag.running_async.store(false, Ordering::Relaxed);
Err(vm.new_stop_iteration(Some(val.0.clone())))
}
val => Ok(val),
@@ -348,13 +346,13 @@ impl PyAsyncGenASend {
}
AwaitableState::Iter => val, // already running, all good
AwaitableState::Init => {
if self.ag.running_async.load() {
if self.ag.running_async.load(Ordering::Relaxed) {
self.state.store(AwaitableState::Closed);
return Err(
vm.new_runtime_error("anext(): asynchronous generator is already running")
);
}
self.ag.running_async.store(true);
self.ag.running_async.store(true, Ordering::Relaxed);
self.state.store(AwaitableState::Iter);
if vm.is_none(&val) {
self.value.clone()
@@ -386,13 +384,13 @@ impl PyAsyncGenASend {
);
}
AwaitableState::Init => {
if self.ag.running_async.load() {
if self.ag.running_async.load(Ordering::Relaxed) {
self.state.store(AwaitableState::Closed);
return Err(
vm.new_runtime_error("anext(): asynchronous generator is already running")
);
}
self.ag.running_async.store(true);
self.ag.running_async.store(true, Ordering::Relaxed);
self.state.store(AwaitableState::Iter);
}
AwaitableState::Iter => {}
@@ -498,7 +496,7 @@ impl PyAsyncGenAThrow {
return Err(vm.new_stop_iteration(None));
}
if matches!(self.state.load(), AwaitableState::Init) {
if self.ag.running_async.load() {
if self.ag.running_async.load(Ordering::Relaxed) {
self.state.store(AwaitableState::Closed);
let msg = if self.aclose {
"aclose(): asynchronous generator is already running"
@@ -519,7 +517,7 @@ impl PyAsyncGenAThrow {
);
}
self.state.store(AwaitableState::Iter);
self.ag.running_async.store(true);
self.ag.running_async.store(true, Ordering::Relaxed);
if self.aclose {
self.ag.ag_closed.store(true);
}
@@ -565,7 +563,7 @@ impl PyAsyncGenAThrow {
return Err(vm.new_runtime_error("cannot reuse already awaited aclose()/athrow()"));
}
AwaitableState::Init => {
if self.ag.running_async.load() {
if self.ag.running_async.load(Ordering::Relaxed) {
self.state.store(AwaitableState::Closed);
let msg = if self.aclose {
"aclose(): asynchronous generator is already running"
@@ -578,7 +576,7 @@ impl PyAsyncGenAThrow {
self.state.store(AwaitableState::Closed);
return Err(vm.new_stop_iteration(None));
}
self.ag.running_async.store(true);
self.ag.running_async.store(true, Ordering::Relaxed);
self.state.store(AwaitableState::Iter);
}
AwaitableState::Iter => {}
@@ -634,12 +632,12 @@ impl PyAsyncGenAThrow {
})
}
fn yield_close(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
self.ag.running_async.store(false);
self.ag.running_async.store(false, Ordering::Relaxed);
self.state.store(AwaitableState::Closed);
vm.new_runtime_error("async generator ignored GeneratorExit")
}
fn check_error(&self, exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyBaseExceptionRef {
self.ag.running_async.store(false);
self.ag.running_async.store(false, Ordering::Relaxed);
self.state.store(AwaitableState::Closed);
if self.aclose
&& (exc.fast_isinstance(vm.ctx.exceptions.stop_async_iteration)
+1 -7
View File
@@ -607,13 +607,7 @@ impl PyRef<PyBytes> {
}
}
/// Return a string decoded from the given bytes.
/// Default encoding is 'utf-8'.
/// Default errors is 'strict', meaning that encoding errors raise a UnicodeError.
/// Other possible values are 'ignore', 'replace'
/// For a list of possible encodings,
/// see https://docs.python.org/3/library/codecs.html#standard-encodings
/// currently, only 'utf-8' and 'ascii' implemented
// currently, only 'utf-8' and 'ascii' implemented
#[pymethod]
fn decode(self, args: DecodeArgs, vm: &VirtualMachine) -> PyResult<PyStrRef> {
bytes_decode(self.into(), args, vm)
+1 -2
View File
@@ -7,8 +7,7 @@ use crate::{
use core::ffi::{CStr, c_void};
use core::sync::atomic::AtomicPtr;
/// PyCapsule - a container for C pointers.
/// In RustPython, this is a minimal implementation for compatibility.
// In RustPython, this is a minimal implementation for compatibility.
#[pyclass(module = false, name = "PyCapsule")]
#[derive(Debug)]
pub struct PyCapsule {
+13 -43
View File
@@ -4,41 +4,23 @@ use super::{
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
class::{PyClassDef, PyClassImpl},
common::lock::PyMutex,
function::{FuncArgs, PySetterValue},
object::PyAtomicRef,
types::{Constructor, GetDescriptor, Initializer, Representable},
};
/// classmethod(function) -> method
///
/// Convert a function to be a class method.
///
/// A class method receives the class as implicit first argument,
/// just like an instance method receives the instance.
/// To declare a class method, use this idiom:
///
/// class C:
/// @classmethod
/// def f(cls, arg1, arg2, ...):
/// ...
///
/// It can be called either on the class (e.g. C.f()) or on an instance
/// (e.g. C().f()). The instance is ignored except for its class.
/// If a class method is called for a derived class, the derived class
/// object is passed as the implied first argument.
///
/// Class methods are different than C++ or Java static methods.
/// If you want those, see the staticmethod builtin.
#[pyclass(module = false, name = "classmethod", traverse)]
#[derive(Debug)]
pub struct PyClassMethod {
callable: PyMutex<PyObjectRef>,
#[pymember(name = "__func__")]
#[pymember(name = "__wrapped__")]
callable: PyAtomicRef<PyObject>,
}
impl From<PyObjectRef> for PyClassMethod {
fn from(callable: PyObjectRef) -> Self {
Self {
callable: PyMutex::new(callable),
callable: PyAtomicRef::from(callable),
}
}
}
@@ -62,7 +44,7 @@ impl GetDescriptor for PyClassMethod {
Some(cls) => cls.to_owned(),
None => _obj.class().to_owned().into(),
};
let callable = zelf.callable.lock().clone();
let callable = zelf.callable.load_owned();
Ok(PyBoundMethod::new(cls, callable).into_ref(&vm.ctx).into())
}
}
@@ -83,7 +65,7 @@ impl Constructor for PyClassMethod {
// `None`, matching CPython.
let _: ClassMethodArgs = args.bind_for(vm, Self::NAME)?;
let classmethod = Self {
callable: PyMutex::new(vm.ctx.none()),
callable: PyAtomicRef::from(vm.ctx.none()),
};
let result = PyRef::new_ref(classmethod, cls, Some(vm.ctx.new_dict()));
Ok(PyObjectRef::from(result))
@@ -99,7 +81,7 @@ impl Initializer for PyClassMethod {
fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
let callable = args.function;
*zelf.callable.lock() = callable.clone();
zelf.callable.store(callable.clone());
functools_wraps(zelf.as_object(), &callable, vm)
}
}
@@ -116,21 +98,9 @@ impl PyClassMethod {
flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
)]
impl PyClassMethod {
#[pymember]
fn __func__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(zelf.callable.lock().clone())
}
#[pymember]
fn __wrapped__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(zelf.callable.lock().clone())
}
#[pygetset]
fn __annotations__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
let callable = zelf.callable.lock();
let callable = zelf.callable.load_owned();
descriptor_get_wrapped_attribute(
&callable,
zelf.as_object(),
@@ -156,7 +126,7 @@ impl PyClassMethod {
#[pygetset]
fn __annotate__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
let callable = zelf.callable.lock();
let callable = zelf.callable.load_owned();
descriptor_get_wrapped_attribute(
&callable,
zelf.as_object(),
@@ -182,7 +152,7 @@ impl PyClassMethod {
#[pygetset]
fn __isabstractmethod__(&self, vm: &VirtualMachine) -> PyObjectRef {
let callable = self.callable.lock().clone();
let callable = self.callable.load_owned();
if let Ok(Some(is_abstract)) = vm.get_attribute_opt(&callable, "__isabstractmethod__") {
is_abstract
} else {
@@ -193,7 +163,7 @@ impl PyClassMethod {
#[pygetset(setter)]
fn set___isabstractmethod__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
self.callable
.lock()
.load_owned()
.set_attr("__isabstractmethod__", value, vm)?;
Ok(())
}
@@ -211,7 +181,7 @@ impl PyClassMethod {
impl Representable for PyClassMethod {
#[inline]
fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
let callable = zelf.callable.lock().repr(vm)?;
let callable = zelf.callable.load_owned().repr(vm)?;
let class = Self::class(&vm.ctx);
let repr = match (
+6 -29
View File
@@ -471,6 +471,12 @@ pub struct CoMonitoringData {
#[pyclass(module = false, name = "code")]
pub struct PyCode {
#[pymember(name = "co_argcount", path = "arg_count")]
#[pymember(name = "co_posonlyargcount", path = "posonlyarg_count")]
#[pymember(name = "co_kwonlyargcount", path = "kwonlyarg_count")]
#[pymember(name = "co_stacksize", path = "max_stackdepth")]
#[pymember(name = "co_name", path = "obj_name")]
#[pymember(name = "co_qualname", path = "qualname")]
pub code: CodeObject,
/// Slot-indexed names, equivalent to CPython's `co_localsplusnames`.
/// Derived once so frame-local proxy operations do not repeatedly scan
@@ -1028,21 +1034,6 @@ impl Constructor for PyCode {
flags(HAS_WEAKREF)
)]
impl PyCode {
#[pygetset]
const fn co_posonlyargcount(&self) -> usize {
self.code.posonlyarg_count as usize
}
#[pygetset]
const fn co_argcount(&self) -> usize {
self.code.arg_count as usize
}
#[pygetset]
const fn co_stacksize(&self) -> u32 {
self.code.max_stackdepth
}
#[pygetset]
pub fn co_filename(&self) -> PyStrRef {
self.source_path().to_owned()
@@ -1068,26 +1059,12 @@ impl PyCode {
self.code.first_line_number.map_or(0, |n| n.get() as _)
}
#[pygetset]
const fn co_kwonlyargcount(&self) -> usize {
self.code.kwonlyarg_count as usize
}
#[pygetset]
fn co_consts(&self, vm: &VirtualMachine) -> PyTupleRef {
let consts = self.code.constants.iter().map(|x| x.0.clone()).collect();
vm.ctx.new_tuple(consts)
}
#[pygetset]
fn co_name(&self) -> PyStrRef {
self.code.obj_name.to_owned()
}
#[pygetset]
fn co_qualname(&self) -> PyStrRef {
self.code.qualname.to_owned()
}
#[pygetset]
fn co_names(&self, vm: &VirtualMachine) -> PyTupleRef {
let names = self
+2 -15
View File
@@ -17,12 +17,11 @@ use num_complex::Complex64;
use num_traits::Zero;
use rustpython_common::hash;
/// Create a complex number from a real part and an optional imaginary part.
///
/// This is equivalent to (real + imag*1j) where imag defaults to 0.
#[pyclass(module = false, name = "complex")]
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct PyComplex {
#[pymember(name = "real", path = "re")]
#[pymember(name = "imag", path = "im")]
value: Complex64,
}
@@ -551,18 +550,6 @@ impl PyComplex {
with(PyRef, Comparable, Hashable, Constructor, AsNumber, Representable)
)]
impl PyComplex {
#[pymember]
fn real(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(vm.ctx.new_float(zelf.as_complex().re).into())
}
#[pymember]
fn imag(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(vm.ctx.new_float(zelf.as_complex().im).into())
}
#[pymethod]
fn conjugate(&self) -> Complex64 {
self.value.conj()
+1 -14
View File
@@ -16,6 +16,7 @@ use crossbeam_utils::atomic::AtomicCell;
// PyCoro_Type in CPython
pub struct PyCoroutine {
inner: Coro,
#[pymember(name = "cr_origin")]
origin: Option<PyTupleRef>,
}
@@ -113,10 +114,6 @@ impl PyCoroutine {
self.inner.code()
}
#[pygetset]
fn cr_origin(&self, _vm: &VirtualMachine) -> Option<PyTupleRef> {
self.origin.clone()
}
#[pygetset]
fn cr_suspended(&self, _vm: &VirtualMachine) -> bool {
self.inner.suspended()
}
@@ -134,20 +131,11 @@ impl PyCoroutine {
#[pyclass]
impl Py<PyCoroutine> {
#[pymethod]
/// send(arg) -> send 'arg' into coroutine,
/// return next iterated value or raise StopIteration.
fn send(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
self.inner.send(self.as_object(), value, vm)
}
#[pymethod]
/// throw(value)
/// throw(type[,value[,traceback]])
///
/// Raise exception in coroutine, return next iterated value or raise
/// StopIteration.
/// the (type, val, tb) signature is deprecated,
/// and may be removed in a future version of Python.
fn throw(
&self,
exc_type: PyObjectRef,
@@ -166,7 +154,6 @@ impl Py<PyCoroutine> {
}
#[pymethod]
/// close() -> raise GeneratorExit inside coroutine.
fn close(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
self.inner.close(self.as_object(), vm)
}
+412 -117
View File
@@ -33,10 +33,11 @@ pub struct PyDescriptorOwned {
#[pyclass(name = "method_descriptor", module = false)]
pub struct PyMethodDescriptor {
#[pymember(name = "__objclass__", path = "typ")]
#[pymember(name = "__name__", path = "name")]
pub common: PyDescriptor,
pub method: &'static PyMethodDef,
// vectorcall: vector_call_func,
pub objclass: &'static Py<PyType>, // TODO: move to tp_members
/// Prevent HeapMethodDef from being freed while this descriptor references it
pub(crate) _method_def_owner: Option<PyObjectRef>,
}
@@ -50,7 +51,6 @@ impl PyMethodDescriptor {
qualname: PyRwLock::new(None),
},
method,
objclass: typ,
_method_def_owner: None,
}
}
@@ -78,6 +78,7 @@ impl GetDescriptor for PyMethodDescriptor {
let descr = Self::_as_pyref(zelf, vm).unwrap();
let bound = match obj {
Some(obj) => {
method_descr_typecheck(descr, obj, vm)?;
if descr.method.flags.contains(PyMethodFlags::METHOD) {
if cls
.as_ref()
@@ -108,6 +109,9 @@ impl Callable for PyMethodDescriptor {
type Args = FuncArgs;
#[inline]
fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
if let Some(obj) = args.args.first() {
method_descr_typecheck(zelf, obj, vm)?;
}
(zelf.method.func)(
vm,
args,
@@ -127,11 +131,6 @@ impl PyMethodDescriptor {
flags(METHOD_DESCRIPTOR, DISALLOW_INSTANTIATION)
)]
impl PyMethodDescriptor {
#[pygetset]
const fn __name__(&self) -> &'static PyStrInterned {
self.common.name
}
#[pygetset]
fn __qualname__(&self) -> String {
format!("{}.{}", self.common.typ.name(), self.common.name)
@@ -149,11 +148,6 @@ impl PyMethodDescriptor {
type_::get_text_signature_from_internal_doc(self.method.name, doc)
}
#[pygetset]
fn __objclass__(&self) -> PyTypeRef {
self.objclass.to_owned()
}
#[pymethod]
fn __reduce__(
&self,
@@ -179,9 +173,10 @@ impl Representable for PyMethodDescriptor {
/// METH_CLASS descriptors. Same layout as method_descriptor; a distinct type.
#[pyclass(name = "classmethod_descriptor", module = false)]
pub struct PyClassMethodDescriptor {
#[pymember(name = "__objclass__", path = "typ")]
#[pymember(name = "__name__", path = "name")]
pub common: PyDescriptor,
pub method: &'static PyMethodDef,
pub objclass: &'static Py<PyType>,
pub(crate) _method_def_owner: Option<PyObjectRef>,
}
@@ -194,7 +189,6 @@ impl PyClassMethodDescriptor {
qualname: PyRwLock::new(None),
},
method,
objclass: typ,
_method_def_owner: None,
}
}
@@ -286,11 +280,6 @@ impl Callable for PyClassMethodDescriptor {
flags(DISALLOW_INSTANTIATION)
)]
impl PyClassMethodDescriptor {
#[pygetset]
const fn __name__(&self) -> &'static PyStrInterned {
self.common.name
}
#[pygetset]
fn __qualname__(&self) -> String {
format!("{}.{}", self.common.typ.name(), self.common.name)
@@ -307,11 +296,6 @@ impl PyClassMethodDescriptor {
let doc = self.method.doc?;
type_::get_text_signature_from_internal_doc(self.method.name, doc)
}
#[pygetset]
fn __objclass__(&self) -> PyTypeRef {
self.objclass.to_owned()
}
}
impl Representable for PyClassMethodDescriptor {
@@ -329,55 +313,140 @@ impl Representable for PyClassMethodDescriptor {
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(i32)]
pub enum MemberKind {
Int = 1,
Double = 4,
Object = 6,
Uint = 11,
Bool = 14,
ObjectEx = 16,
/// `Py_ssize_t`. Writable cells are `AtomicIsize`.
PySsizeT = 19,
}
impl MemberKind {
#[must_use]
pub fn from_i32(value: i32) -> Option<Self> {
match value {
1 => Some(Self::Int),
4 => Some(Self::Double),
6 => Some(Self::Object),
11 => Some(Self::Uint),
14 => Some(Self::Bool),
16 => Some(Self::ObjectEx),
19 => Some(Self::PySsizeT),
_ => None,
}
}
}
pub const PY_READONLY: i32 = 1;
pub(crate) const PY_AUDIT_READ: i32 = 2;
#[doc(hidden)]
pub const PY_AUDIT_READ: i32 = 2;
pub const PY_RELATIVE_OFFSET: i32 = 8;
pub(crate) type MemberSetterFunc =
Option<fn(&VirtualMachine, PyObjectRef, PySetterValue) -> PyResult<()>>;
/// Kind of a `#[pymember]` field. The macro reads [`MemberLayout::KIND`].
#[doc(hidden)]
pub trait MemberLayout {
const KIND: MemberKind;
}
/// Writable `#[pymember]` field. Only an atomic cell may change after publication.
#[doc(hidden)]
pub trait MemberCell: MemberLayout {}
/// `KIND` of the field named by `probe`. `probe` is not called.
#[doc(hidden)]
#[must_use]
pub const fn member_kind_of<T: MemberLayout, Owner>(
_: for<'a> fn(&'a Owner) -> &'a T,
) -> MemberKind {
<T as MemberLayout>::KIND
}
impl MemberLayout for bool {
const KIND: MemberKind = MemberKind::Bool;
}
impl MemberLayout for core::sync::atomic::AtomicBool {
const KIND: MemberKind = MemberKind::Bool;
}
impl MemberCell for core::sync::atomic::AtomicBool {}
impl MemberLayout for i32 {
const KIND: MemberKind = MemberKind::Int;
}
impl MemberLayout for core::sync::atomic::AtomicI32 {
const KIND: MemberKind = MemberKind::Int;
}
impl MemberCell for core::sync::atomic::AtomicI32 {}
impl MemberLayout for u32 {
const KIND: MemberKind = MemberKind::Uint;
}
impl MemberLayout for core::sync::atomic::AtomicU32 {
const KIND: MemberKind = MemberKind::Uint;
}
impl MemberCell for core::sync::atomic::AtomicU32 {}
// One object pointer. Readonly members may be a plain pointer. Writable
// members are an atomic cell: `PyAtomicRef<PyObject>` when the pointer is
// never null, or `PyAtomicRef<Option<PyObject>>` when it may be.
impl MemberLayout for PyObjectRef {
const KIND: MemberKind = MemberKind::Object;
}
impl MemberLayout for Option<PyObjectRef> {
const KIND: MemberKind = MemberKind::Object;
}
impl<T> MemberLayout for PyRef<T> {
const KIND: MemberKind = MemberKind::Object;
}
impl<T> MemberLayout for Option<PyRef<T>> {
const KIND: MemberKind = MemberKind::Object;
}
impl MemberLayout for crate::object::PyAtomicRef<PyObject> {
const KIND: MemberKind = MemberKind::Object;
}
impl MemberLayout for crate::object::PyAtomicRef<Option<PyObject>> {
const KIND: MemberKind = MemberKind::Object;
}
impl<T: PyPayload> MemberLayout for crate::object::PyAtomicRef<Option<T>> {
const KIND: MemberKind = MemberKind::Object;
}
impl MemberLayout for &'static crate::builtins::PyStrInterned {
const KIND: MemberKind = MemberKind::Object;
}
impl<T: PyPayload> MemberLayout for &'static Py<T> {
const KIND: MemberKind = MemberKind::Object;
}
impl MemberCell for crate::object::PyAtomicRef<PyObject> {}
impl MemberCell for crate::object::PyAtomicRef<Option<PyObject>> {}
impl MemberLayout for f64 {
const KIND: MemberKind = MemberKind::Double;
}
impl MemberLayout for crate::common::atomic::AtomicF64 {
const KIND: MemberKind = MemberKind::Double;
}
impl MemberCell for crate::common::atomic::AtomicF64 {}
impl MemberLayout for isize {
const KIND: MemberKind = MemberKind::PySsizeT;
}
impl MemberLayout for core::sync::atomic::AtomicIsize {
const KIND: MemberKind = MemberKind::PySsizeT;
}
impl MemberCell for core::sync::atomic::AtomicIsize {}
/// Where `PyMemberDef.offset` points.
///
/// Slot members use a byte offset from the object to an inline pointer cell.
/// Builtin payloads keep fields behind locks, so those members use a function
/// and ignore `offset`.
#[derive(Clone, Copy)]
/// `Offset` is a byte offset from the object to the field.
/// `TupleItem` is a struct-sequence element index: the elements live in the
/// tuple payload, not as separately addressable fields.
#[derive(Clone, Copy, Debug)]
pub enum MemberAccess {
Func {
get: fn(&VirtualMachine, PyObjectRef) -> PyResult,
set: MemberSetterFunc,
},
Slot,
Offset,
TupleItem,
}
impl core::fmt::Debug for MemberAccess {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Func { set, .. } => f.debug_struct("Func").field("set", &set.is_some()).finish(),
Self::Slot => write!(f, "Slot"),
Self::TupleItem => write!(f, "TupleItem"),
}
}
}
/// Same fields as `PyMemberDef`: name, type, offset, flags, doc.
pub struct PyMemberDef {
pub name: String,
@@ -391,6 +460,10 @@ impl PyMemberDef {
pub(crate) fn readonly(&self) -> bool {
self.flags & PY_READONLY != 0
}
pub(crate) fn audit_read(&self) -> bool {
self.flags & PY_AUDIT_READ != 0
}
}
impl core::fmt::Debug for PyMemberDef {
@@ -405,21 +478,69 @@ impl core::fmt::Debug for PyMemberDef {
}
}
/// Const-constructible member spec. Registered as a `PyMemberDef`.
#[derive(Clone, Copy)]
pub struct PyMemberSpec {
pub name: &'static str,
pub kind: MemberKind,
pub offset: isize,
pub flags: i32,
pub doc: Option<&'static str>,
}
impl PyMemberSpec {
/// Concatenate cfg-gated member groups into one table.
#[must_use]
pub const fn concat<const N: usize>(parts: &[&[Self]]) -> [Self; N] {
const EMPTY: PyMemberSpec = PyMemberSpec {
name: "",
kind: MemberKind::Object,
offset: 0,
flags: 0,
doc: None,
};
let mut out = [EMPTY; N];
let mut index = 0;
let mut part_index = 0;
while part_index < parts.len() {
let part = parts[part_index];
let mut item_index = 0;
while item_index < part.len() {
out[index] = part[item_index];
index += 1;
item_index += 1;
}
part_index += 1;
}
out
}
}
// = PyMemberDescrObject
#[pyclass(name = "member_descriptor", module = false)]
#[derive(Debug)]
pub struct PyMemberDescriptor {
#[pymember(name = "__objclass__", path = "typ")]
#[pymember(name = "__name__", path = "name")]
pub common: PyDescriptorOwned,
pub member: PyMemberDef,
pub access: MemberAccess,
}
impl PyMemberDescriptor {
/// Byte offset of an instance-slot member. `None` when this is not a slot.
/// Byte offset of an object-pointer member. `None` for bool, float, and
/// struct-sequence indexes, which slot specialization must not treat as cells.
pub(crate) fn slot_offset(&self) -> Option<isize> {
match self.access {
MemberAccess::Slot => Some(self.member.offset),
_ => None,
if matches!(self.access, MemberAccess::TupleItem) {
return None;
}
match self.member.kind {
MemberKind::Object | MemberKind::ObjectEx => Some(self.member.offset),
MemberKind::Bool
| MemberKind::Double
| MemberKind::Int
| MemberKind::Uint
| MemberKind::PySsizeT => None,
}
}
@@ -430,8 +551,7 @@ impl PyMemberDescriptor {
})?;
}
match self.access {
MemberAccess::Func { get, .. } => get(vm, obj),
MemberAccess::Slot => get_slot_from_object(&obj, self.member.offset, &self.member, vm),
MemberAccess::Offset => member_get_one(&obj, self.member.offset, &self.member, vm),
MemberAccess::TupleItem => {
let index = self.member.offset as usize;
let tuple = obj.downcast_ref::<PyTuple>().ok_or_else(|| {
@@ -456,12 +576,8 @@ impl PyMemberDescriptor {
return Err(vm.new_attribute_error("readonly attribute"));
}
match self.access {
MemberAccess::Func { set, .. } => match set {
Some(set) => set(vm, obj, value),
None => Err(vm.new_attribute_error("readonly attribute")),
},
MemberAccess::Slot => {
set_slot_at_object(&obj, self.member.offset, &self.member, value, vm)
MemberAccess::Offset => {
member_set_one(&obj, self.member.offset, &self.member, value, vm)
}
MemberAccess::TupleItem => Err(vm.new_attribute_error("readonly attribute")),
}
@@ -487,18 +603,6 @@ fn calculate_qualname(descr: &PyDescriptorOwned, vm: &VirtualMachine) -> PyResul
#[pyclass(with(GetDescriptor, Representable), flags(DISALLOW_INSTANTIATION))]
impl PyMemberDescriptor {
#[pymember]
fn __objclass__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(zelf.common.typ.clone().into())
}
#[pymember]
fn __name__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(zelf.common.name.to_owned().into())
}
#[pygetset]
fn __doc__(&self) -> Option<String> {
self.member.doc.to_owned()
@@ -556,18 +660,86 @@ impl PyMemberDescriptor {
}
}
// PyMember_GetOne for a value stored in the instance slot array.
fn get_slot_from_object(
fn member_addr(obj: &PyObject, offset: isize) -> *mut u8 {
(obj as *const PyObject as *const u8).wrapping_add(offset as usize) as *mut u8
}
fn warn_member(vm: &VirtualMachine, message: &str) -> PyResult<()> {
crate::warn::warn(
vm.ctx.new_str(message).into(),
Some(vm.ctx.exceptions.runtime_warning.to_owned()),
1,
None,
vm,
)
}
fn load_i32(obj: &PyObject, offset: isize, readonly: bool) -> i32 {
let addr = member_addr(obj, offset);
if readonly {
// SAFETY: a readonly int member addresses an `i32` that is not written
// after publication.
unsafe { addr.cast::<i32>().read() }
} else {
// SAFETY: a writable int member addresses an aligned `AtomicI32`.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicI32>())
.load(core::sync::atomic::Ordering::Relaxed)
}
}
}
fn load_u32(obj: &PyObject, offset: isize, readonly: bool) -> u32 {
let addr = member_addr(obj, offset);
if readonly {
// SAFETY: a readonly uint member addresses a `u32` that is not written
// after publication.
unsafe { addr.cast::<u32>().read() }
} else {
// SAFETY: a writable uint member addresses an aligned `AtomicU32`.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicU32>())
.load(core::sync::atomic::Ordering::Relaxed)
}
}
}
fn member_as_c_long(value: &PyObject, vm: &VirtualMachine) -> PyResult<core::ffi::c_long> {
let int_obj = value.try_index(vm)?;
core::ffi::c_long::try_from(int_obj.as_bigint())
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C long"))
}
fn member_uint_value(
value: &PyObject,
vm: &VirtualMachine,
) -> PyResult<(u32, Option<&'static str>)> {
let int_obj = value.try_index(vm)?;
let big = int_obj.as_bigint();
if big.sign() == malachite_bigint::Sign::Minus {
let long_val = core::ffi::c_long::try_from(big)
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C long"))?;
// Keeps the low 32 bits whether `c_long` is 32 or 64 bits wide.
let stored = long_val as u32;
return Ok((stored, Some("Writing negative value into unsigned field")));
}
core::ffi::c_ulong::try_from(big)
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C unsigned long"))?;
let wide = u64::try_from(big).expect("a value that fits c_ulong fits u64");
let stored = wide as u32;
let warning = (wide > u64::from(u32::MAX)).then_some("Truncation of value to unsigned int");
Ok((stored, warning))
}
// PyMember_GetOne. `offset` is a byte offset from the object to the field.
fn member_get_one(
obj: &PyObject,
offset: isize,
member: &PyMemberDef,
vm: &VirtualMachine,
) -> PyResult {
let slot = match member.kind {
let value = match member.kind {
MemberKind::Object => obj.get_slot(offset).unwrap_or_else(|| vm.ctx.none()),
MemberKind::Bool => obj
.get_slot(offset)
.unwrap_or_else(|| vm.ctx.new_bool(false).into()),
MemberKind::ObjectEx => obj.get_slot(offset).ok_or_else(|| {
vm.new_attribute_error(format!(
"'{}' object has no attribute '{}'",
@@ -575,12 +747,62 @@ fn get_slot_from_object(
member.name
))
})?,
MemberKind::Bool => {
// SAFETY: a bool member addresses an `AtomicBool` or a `bool`
// (one byte, naturally aligned). The macro rejects any other field
// type. A plain `bool` is not written after the object is published.
let raw = unsafe {
(*member_addr(obj, offset).cast::<core::sync::atomic::AtomicBool>())
.load(core::sync::atomic::Ordering::Relaxed)
};
vm.ctx.new_bool(raw).into()
}
MemberKind::Int => vm
.ctx
.new_int(load_i32(obj, offset, member.readonly()))
.into(),
MemberKind::Uint => vm
.ctx
.new_int(load_u32(obj, offset, member.readonly()))
.into(),
MemberKind::Double => {
let raw = if member.readonly() {
// SAFETY: a readonly double member addresses an `f64` that is
// not written after publication. A plain `f64` may be only
// 4-byte aligned, so this is a plain read and not an atomic
// access.
unsafe { member_addr(obj, offset).cast::<f64>().read() }
} else {
// SAFETY: a writable double member addresses an aligned
// `AtomicF64`. The macro accepts only `MemberCell`.
unsafe {
(*member_addr(obj, offset).cast::<crate::common::atomic::AtomicF64>())
.load(core::sync::atomic::Ordering::Relaxed)
}
};
vm.ctx.new_float(raw).into()
}
MemberKind::PySsizeT => {
let raw = if member.readonly() {
// SAFETY: a readonly `Py_ssize_t` member addresses an `isize`
// that is not written after publication.
unsafe { member_addr(obj, offset).cast::<isize>().read() }
} else {
// SAFETY: a writable `Py_ssize_t` member addresses an aligned
// `AtomicIsize`. The macro accepts only `MemberCell`.
unsafe {
(*member_addr(obj, offset).cast::<core::sync::atomic::AtomicIsize>())
.load(core::sync::atomic::Ordering::Relaxed)
}
};
vm.ctx.new_int(raw).into()
}
};
Ok(slot)
Ok(value)
}
// PyMember_SetOne for a value stored in the instance slot array.
fn set_slot_at_object(
// PyMember_SetOne.
fn member_set_one(
obj: &PyObject,
offset: isize,
member: &PyMemberDef,
@@ -588,35 +810,17 @@ fn set_slot_at_object(
vm: &VirtualMachine,
) -> PyResult<()> {
if matches!(value, PySetterValue::Delete)
&& member.kind != MemberKind::ObjectEx
&& member.kind != MemberKind::Object
&& !matches!(member.kind, MemberKind::Object | MemberKind::ObjectEx)
{
return Err(vm.new_type_error("can't delete numeric/char attribute"));
}
match member.kind {
MemberKind::Object => match value {
PySetterValue::Assign(v) => {
obj.set_slot(offset, Some(v));
}
PySetterValue::Delete => {
obj.set_slot(offset, None);
}
},
MemberKind::Bool => match value {
PySetterValue::Assign(v) => {
if !v.class().is(vm.ctx.types.bool_type) {
return Err(vm.new_type_error("attribute value type must be bool"));
}
obj.set_slot(offset, Some(v))
}
PySetterValue::Delete => {
return Err(vm.new_type_error("can't delete numeric/char attribute"));
}
PySetterValue::Assign(v) => obj.set_slot(offset, Some(v)),
PySetterValue::Delete => obj.set_slot(offset, None),
},
MemberKind::ObjectEx => match value {
PySetterValue::Assign(v) => {
obj.set_slot(offset, Some(v));
}
PySetterValue::Assign(v) => obj.set_slot(offset, Some(v)),
PySetterValue::Delete => {
if obj.get_slot(offset).is_none() {
return Err(vm.new_attribute_error(member.name.clone()));
@@ -624,8 +828,87 @@ fn set_slot_at_object(
obj.set_slot(offset, None);
}
},
MemberKind::Bool => {
let PySetterValue::Assign(value) = value else {
return Err(vm.new_type_error("can't delete numeric/char attribute"));
};
if !value.class().is(vm.ctx.types.bool_type) {
return Err(vm.new_type_error("attribute value type must be bool"));
}
let stored = value.is(&vm.ctx.true_value);
// SAFETY: a writable bool member addresses an `AtomicBool`.
unsafe {
(*member_addr(obj, offset).cast::<core::sync::atomic::AtomicBool>())
.store(stored, core::sync::atomic::Ordering::Relaxed);
}
}
MemberKind::Int => {
let PySetterValue::Assign(value) = value else {
return Err(vm.new_type_error("can't delete numeric/char attribute"));
};
let long_val = member_as_c_long(&value, vm)?;
let stored = long_val as i32;
// SAFETY: a writable int member addresses an aligned `AtomicI32`.
// Readonly members are rejected before this call.
unsafe {
(*member_addr(obj, offset).cast::<core::sync::atomic::AtomicI32>())
.store(stored, core::sync::atomic::Ordering::Relaxed);
}
let truncated = long_val > i32::MAX as core::ffi::c_long
|| long_val < i32::MIN as core::ffi::c_long;
if truncated {
warn_member(vm, "Truncation of value to int")?;
}
}
MemberKind::Uint => {
let PySetterValue::Assign(value) = value else {
return Err(vm.new_type_error("can't delete numeric/char attribute"));
};
let (stored, warning) = member_uint_value(&value, vm)?;
// SAFETY: a writable uint member addresses an aligned `AtomicU32`.
// Readonly members are rejected before this call.
unsafe {
(*member_addr(obj, offset).cast::<core::sync::atomic::AtomicU32>())
.store(stored, core::sync::atomic::Ordering::Relaxed);
}
if let Some(warning) = warning {
warn_member(vm, warning)?;
}
}
MemberKind::Double => {
let PySetterValue::Assign(value) = value else {
return Err(vm.new_type_error("can't delete numeric/char attribute"));
};
let number = value.try_float(vm)?.to_f64();
// SAFETY: a writable double member addresses an aligned `AtomicF64`.
// Readonly members are rejected before this call.
unsafe {
(*member_addr(obj, offset).cast::<crate::common::atomic::AtomicF64>())
.store(number, core::sync::atomic::Ordering::Relaxed);
}
}
MemberKind::PySsizeT => {
let PySetterValue::Assign(value) = value else {
return Err(vm.new_type_error("can't delete numeric/char attribute"));
};
// PyLong_AsSsize_t: an int (bool included). No `__index__`.
if !value.fast_isinstance(vm.ctx.types.int_type) {
return Err(vm.new_type_error("an integer is required"));
}
let Some(int_obj) = value.downcast_ref::<crate::builtins::PyInt>() else {
return Err(vm.new_type_error("an integer is required"));
};
let stored = isize::try_from(int_obj.as_bigint()).map_err(|_| {
vm.new_overflow_error("Python int too large to convert to C ssize_t")
})?;
// SAFETY: a writable `Py_ssize_t` member addresses an aligned
// `AtomicIsize`. Readonly members are rejected before this call.
unsafe {
(*member_addr(obj, offset).cast::<core::sync::atomic::AtomicIsize>())
.store(stored, core::sync::atomic::Ordering::Relaxed);
}
}
}
Ok(())
}
@@ -665,6 +948,25 @@ impl GetDescriptor for PyMemberDescriptor {
}
}
fn method_descr_typecheck(
descr: &PyMethodDescriptor,
obj: &PyObject,
vm: &VirtualMachine,
) -> PyResult<()> {
if descr.method.flags.contains(PyMethodFlags::STATIC)
|| descr.method.flags.contains(PyMethodFlags::CLASS)
|| obj.fast_isinstance(descr.common.typ)
{
return Ok(());
}
Err(vm.new_type_error(format!(
"descriptor '{}' for '{}' objects doesn't apply to a '{}' object",
descr.common.name.as_str(),
descr.common.typ.name(),
obj.class().name()
)))
}
/// Vectorcall for method_descriptor: calls native method directly
fn vectorcall_method_descriptor(
zelf_obj: &PyObject,
@@ -674,6 +976,11 @@ fn vectorcall_method_descriptor(
vm: &VirtualMachine,
) -> PyResult {
let zelf: &Py<PyMethodDescriptor> = zelf_obj.downcast_ref().unwrap();
if nargs > 0
&& let Some(obj) = args.first()
{
method_descr_typecheck(zelf, obj, vm)?;
}
let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
(zelf.method.func)(
vm,
@@ -1058,7 +1365,9 @@ fn parse_buffer_flags(
#[pyclass(name = "wrapper_descriptor", module = false)]
#[derive(Debug)]
pub(crate) struct PyWrapper {
#[pymember(name = "__objclass__")]
pub typ: &'static Py<PyType>,
#[pymember(name = "__name__")]
pub name: &'static PyStrInterned,
pub wrapped: SlotFunc,
/// Slot text, including the text signature.
@@ -1119,21 +1428,11 @@ impl Callable for PyWrapper {
flags(DISALLOW_INSTANTIATION)
)]
impl PyWrapper {
#[pygetset]
fn __name__(&self) -> &'static PyStrInterned {
self.name
}
#[pygetset]
fn __qualname__(&self) -> String {
format!("{}.{}", self.typ.name(), self.name)
}
#[pygetset]
fn __objclass__(&self) -> PyTypeRef {
self.typ.to_owned()
}
#[pygetset]
fn __doc__(&self) -> Option<&'static str> {
if let Some(doc) = self.plain_doc {
@@ -1171,6 +1470,7 @@ impl Representable for PyWrapper {
#[derive(Debug)]
pub(crate) struct PyMethodWrapper {
pub wrapper: PyRef<PyWrapper>,
#[pymember(name = "__self__")]
#[pytraverse(skip)]
pub obj: PyObjectRef,
}
@@ -1203,11 +1503,6 @@ impl Callable for PyMethodWrapper {
flags(DISALLOW_INSTANTIATION)
)]
impl PyMethodWrapper {
#[pygetset]
fn __self__(&self) -> PyObjectRef {
self.obj.clone()
}
#[pygetset]
fn __name__(&self) -> &'static PyStrInterned {
self.wrapper.name
-1
View File
@@ -590,7 +590,6 @@ impl Py<PyDict> {
Ok(PyArithmeticValue::Implemented(true))
}
/// Return self[key].
#[cfg_attr(feature = "flame-it", flame("PyDictRef"))]
#[pymethod(coexist)]
fn __getitem__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult {
+9 -54
View File
@@ -6,7 +6,7 @@ use super::{PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyIntRef};
use crate::{
AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
class::PyClassImpl,
frame::{FrameObject, FrameObjectRef, FrameOwner},
frame::{FrameObject, FrameOwner},
function::PySetterValue,
types::{Destructor, Representable},
};
@@ -771,69 +771,24 @@ impl FrameObject {
}
}
#[expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")]
#[pymember(type = "bool")]
fn f_trace_lines(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: FrameObjectRef = zelf.downcast().unwrap_or_else(|_| unreachable!());
let boxed = zelf.iframe().cold().trace_lines.lock();
Ok(vm.ctx.new_bool(*boxed).into())
#[pygetset]
fn f_trace_opcodes(&self, vm: &VirtualMachine) -> PyObjectRef {
let trace_opcodes = self.iframe().cold().trace_opcodes.lock();
vm.ctx.new_bool(*trace_opcodes).into()
}
#[pymember(type = "bool", setter)]
fn set_f_trace_lines(
vm: &VirtualMachine,
zelf: PyObjectRef,
value: PySetterValue,
) -> PyResult<()> {
#[pygetset(setter)]
fn set_f_trace_opcodes(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
match value {
PySetterValue::Assign(value) => {
let zelf: FrameObjectRef = zelf.downcast().unwrap_or_else(|_| unreachable!());
let value: PyIntRef = value
.downcast()
.map_err(|_| vm.new_type_error("attribute value type must be bool"))?;
let val = !value.as_bigint().is_zero();
*zelf.iframe().cold().trace_lines.lock() = val;
*self.iframe().cold().trace_opcodes.lock() = val;
// Propagate to live source iframe.
let live = zelf.find_live_source_iframe();
if !live.is_null() {
*unsafe { &*live }.cold().trace_lines.lock() = val;
}
Ok(())
}
PySetterValue::Delete => Err(vm.new_type_error("can't delete numeric/char attribute")),
}
}
#[expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")]
#[pymember(type = "bool")]
fn f_trace_opcodes(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: FrameObjectRef = zelf.downcast().unwrap_or_else(|_| unreachable!());
let trace_opcodes = zelf.iframe().cold().trace_opcodes.lock();
Ok(vm.ctx.new_bool(*trace_opcodes).into())
}
#[pymember(type = "bool", setter)]
fn set_f_trace_opcodes(
vm: &VirtualMachine,
zelf: PyObjectRef,
value: PySetterValue,
) -> PyResult<()> {
match value {
PySetterValue::Assign(value) => {
let zelf: FrameObjectRef = zelf.downcast().unwrap_or_else(|_| unreachable!());
let value: PyIntRef = value
.downcast()
.map_err(|_| vm.new_type_error("attribute value type must be bool"))?;
let val = !value.as_bigint().is_zero();
*zelf.iframe().cold().trace_opcodes.lock() = val;
// Propagate to live source iframe.
let live = zelf.find_live_source_iframe();
let live = self.find_live_source_iframe();
if !live.is_null() {
*unsafe { &*live }.cold().trace_opcodes.lock() = val;
}
+17 -78
View File
@@ -65,8 +65,11 @@ fn format_missing_args(
#[derive(Debug)]
pub struct PyFunction {
pub(crate) code: PyAtomicRef<PyCode>,
#[pymember(name = "__globals__")]
pub(crate) globals: PyDictRef,
#[pymember(name = "__builtins__")]
pub(crate) builtins: PyObjectRef,
#[pymember(name = "__closure__")]
pub(crate) closure: Option<PyRef<PyTuple<PyCellRef>>>,
defaults_and_kwdefaults: PyMutex<(Option<PyTupleRef>, Option<PyDictRef>)>,
name: PyMutex<PyStrRef>,
@@ -74,8 +77,10 @@ pub struct PyFunction {
type_params: PyMutex<PyTupleRef>,
annotations: PyMutex<Option<PyDictRef>>,
annotate: PyMutex<Option<PyObjectRef>>,
module: PyMutex<PyObjectRef>,
doc: PyMutex<PyObjectRef>,
#[pymember(name = "__module__", writable)]
module: PyAtomicRef<Option<PyObject>>,
#[pymember(name = "__doc__", writable)]
doc: PyAtomicRef<Option<PyObject>>,
func_version: AtomicU32,
#[cfg(feature = "jit")]
jitted_code: PyMutex<Option<CompiledCode>>,
@@ -106,8 +111,8 @@ unsafe impl Traverse for PyFunction {
// Traverse additional fields that may contain references
self.type_params.lock().traverse(tracer_fn);
self.annotations.lock().traverse(tracer_fn);
self.module.lock().traverse(tracer_fn);
self.doc.lock().traverse(tracer_fn);
self.module.traverse(tracer_fn);
self.doc.traverse(tracer_fn);
}
fn clear(&mut self, out: &mut Vec<crate::PyObjectRef>) {
@@ -139,14 +144,10 @@ unsafe impl Traverse for PyFunction {
}
// Clear module, doc, and type_params (Py_CLEAR)
if let Some(mut guard) = self.module.try_lock() {
let old_module =
core::mem::replace(&mut *guard, Context::genesis().none.to_owned().into());
if let Some(old_module) = self.module.store(Some(Context::genesis().none())) {
out.push(old_module);
}
if let Some(mut guard) = self.doc.try_lock() {
let old_doc =
core::mem::replace(&mut *guard, Context::genesis().none.to_owned().into());
if let Some(old_doc) = self.doc.store(Some(Context::genesis().none())) {
out.push(old_doc);
}
if let Some(mut guard) = self.type_params.try_lock() {
@@ -213,8 +214,8 @@ impl PyFunction {
type_params: PyMutex::new(vm.ctx.empty_tuple.clone()),
annotations: PyMutex::new(None),
annotate: PyMutex::new(None),
module: PyMutex::new(module),
doc: PyMutex::new(doc),
module: PyAtomicRef::from(Some(module)),
doc: PyAtomicRef::from(Some(doc)),
func_version: AtomicU32::new(next_func_version()),
#[cfg(feature = "jit")]
jitted_code: PyMutex::new(None),
@@ -536,7 +537,8 @@ impl PyFunction {
})?
.into_pyref();
self.closure = Some(closure_tuple.try_into_typed::<PyCell>(vm)?);
let typed = closure_tuple.try_into_typed::<PyCell>(vm)?;
self.closure = Some(typed);
}
bytecode::MakeFunctionFlag::TypeParams => {
let type_params = attr_value.clone().downcast::<PyTuple>().map_err(|_| {
@@ -1051,29 +1053,6 @@ impl PyFunction {
crate::stdlib::_testinternalcapi::note_func_modification();
}
// {"__closure__", T_OBJECT, OFF(func_closure), READONLY},
// {"__doc__", T_OBJECT, OFF(func_doc), 0},
// {"__globals__", T_OBJECT, OFF(func_globals), READONLY},
// {"__module__", T_OBJECT, OFF(func_module), 0},
// {"__builtins__", T_OBJECT, OFF(func_builtins), READONLY},
#[pymember]
fn __globals__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf = Self::_as_pyref(&zelf, vm)?;
Ok(zelf.globals.clone().into())
}
#[pymember]
fn __closure__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf = Self::_as_pyref(&zelf, vm)?;
Ok(vm.unwrap_or_none(zelf.closure.clone().map(|x| x.into())))
}
#[pymember]
fn __builtins__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf = Self::_as_pyref(&zelf, vm)?;
Ok(zelf.builtins.clone())
}
#[pygetset]
fn __name__(&self) -> PyStrRef {
self.name.lock().clone()
@@ -1084,38 +1063,6 @@ impl PyFunction {
*self.name.lock() = name;
}
#[expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")]
#[pymember]
fn __doc__(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult {
// When accessed from instance, obj is the PyFunction instance
if let Ok(func) = obj.downcast::<Self>() {
let doc = func.doc.lock();
Ok(doc.clone())
} else {
// When accessed from class, return None as there's no instance
Ok(vm.ctx.none())
}
}
#[expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")]
#[pymember(setter)]
fn set___doc__(vm: &VirtualMachine, zelf: PyObjectRef, value: PySetterValue) -> PyResult<()> {
let zelf: PyRef<Self> = zelf.downcast().unwrap_or_else(|_| unreachable!());
let value = value.unwrap_or_none(vm);
*zelf.doc.lock() = value;
Ok(())
}
#[pygetset]
fn __module__(&self) -> PyObjectRef {
self.module.lock().clone()
}
#[pygetset(setter)]
fn set___module__(&self, module: PySetterValue<PyObjectRef>, vm: &VirtualMachine) {
*self.module.lock() = module.unwrap_or_none(vm);
}
#[pygetset]
fn __annotations__(&self, vm: &VirtualMachine) -> PyResult<PyDictRef> {
// First check if we have cached annotations
@@ -1393,7 +1340,9 @@ impl Constructor for PyFunction {
#[pyclass(module = false, name = "method", traverse)]
#[derive(Debug)]
pub struct PyBoundMethod {
#[pymember(name = "__self__")]
object: PyObjectRef,
#[pymember(name = "__func__")]
function: PyObjectRef,
}
@@ -1531,16 +1480,6 @@ impl PyBoundMethod {
self.function.get_attr("__doc__", vm)
}
#[pygetset]
fn __func__(&self) -> PyObjectRef {
self.function.clone()
}
#[pygetset(name = "__self__")]
fn get_self(&self) -> PyObjectRef {
self.object.clone()
}
#[pygetset]
fn __module__(&self, vm: &VirtualMachine) -> Option<PyObjectRef> {
self.function.get_attr("__module__", vm).ok()
+10 -22
View File
@@ -36,9 +36,12 @@ static ATTR_BLOCKED: [&str; 3] = ["__bases__", "__copy__", "__deepcopy__"];
#[pyclass(module = "types", name = "GenericAlias")]
pub struct PyGenericAlias {
#[pymember(name = "__origin__")]
origin: PyObjectRef,
#[pymember(name = "__args__")]
args: PyTupleRef,
parameters: PyTupleRef,
#[pymember(name = "__unpacked__")]
starred: bool, // for __unpacked__ attribute
}
@@ -221,21 +224,6 @@ impl PyGenericAlias {
self.parameters.clone().into()
}
#[pygetset]
fn __args__(&self) -> PyObjectRef {
self.args.clone().into()
}
#[pygetset]
fn __origin__(&self) -> PyObjectRef {
self.origin.clone()
}
#[pygetset]
const fn __unpacked__(&self) -> bool {
self.starred
}
#[pygetset]
fn __typing_unpacked_tuple_args__(&self, vm: &VirtualMachine) -> PyObjectRef {
if self.starred && self.origin.is(vm.ctx.types.tuple_type.as_object()) {
@@ -253,7 +241,7 @@ impl PyGenericAlias {
#[pymethod]
fn __dir__(&self, vm: &VirtualMachine) -> PyResult<PyList> {
let dir = vm.dir(Some(self.__origin__()))?;
let dir = vm.dir(Some(self.origin.clone()))?;
for exc in &ATTR_EXCEPTIONS {
let exc_obj = (*exc).to_pyobject(vm);
if !dir.__contains__(&exc_obj, vm)? {
@@ -291,7 +279,7 @@ impl PyGenericAlias {
#[pymethod]
fn __mro_entries__(&self, _object: PyObjectRef, vm: &VirtualMachine) -> PyTupleRef {
PyTuple::new_ref(vec![self.__origin__()], &vm.ctx)
PyTuple::new_ref(vec![self.origin.clone()], &vm.ctx)
}
#[pymethod]
@@ -643,10 +631,10 @@ impl Comparable for PyGenericAlias {
return Ok(PyComparisonValue::Implemented(false));
}
Ok(PyComparisonValue::Implemented(
zelf.__origin__()
.rich_compare_bool(&other.__origin__(), PyComparisonOp::Eq, vm)?
&& zelf.__args__().rich_compare_bool(
&other.__args__(),
zelf.origin
.rich_compare_bool(&other.origin, PyComparisonOp::Eq, vm)?
&& zelf.args.as_object().rich_compare_bool(
other.args.as_object(),
PyComparisonOp::Eq,
vm,
)?,
@@ -675,7 +663,7 @@ impl GetAttr for PyGenericAlias {
return zelf.as_object().generic_getattr(attr, vm);
}
}
zelf.__origin__().get_attr(attr, vm)
zelf.origin.get_attr(attr, vm)
}
}
+13 -22
View File
@@ -11,9 +11,11 @@ use crate::{
#[pyclass(module = false, name = "getset_descriptor", traverse = "manual")]
pub struct PyGetSet {
name: String,
/// `d_type`. Owned: a type's namespace can outlive the type, and the
/// descriptors it holds have to stay valid for as long as it does.
#[pymember(name = "__name__")]
name: &'static crate::builtins::PyStrInterned,
// `d_type`. Owned: a type's namespace can outlive the type, and the
// descriptors it holds have to stay valid for as long as it does.
#[pymember(name = "__objclass__")]
class: PyRef<PyType>,
getter: Option<PyGetterFunc>,
setter: Option<PySetterFunc>,
@@ -25,7 +27,7 @@ impl core::fmt::Debug for PyGetSet {
write!(
f,
"PyGetSet {{ name: {}, getter: {}, setter: {} }}",
self.name,
self.name.as_str(),
if self.getter.is_some() {
"Some"
} else {
@@ -70,7 +72,7 @@ impl GetDescriptor for PyGetSet {
} else {
Err(vm.new_attribute_error(format!(
"attribute '{}' of '{}' objects is not readable",
zelf.name,
zelf.name.as_str(),
Self::class(&vm.ctx).name()
)))
}
@@ -79,9 +81,9 @@ impl GetDescriptor for PyGetSet {
impl PyGetSet {
#[must_use]
pub fn new(name: &str, class: &Py<PyType>) -> Self {
pub fn new(name: &str, class: &Py<PyType>, ctx: &Context) -> Self {
Self {
name: name.into(),
name: ctx.intern_str(name),
class: class.to_owned(),
getter: None,
setter: None,
@@ -131,32 +133,21 @@ impl PyGetSet {
} else {
Err(vm.new_attribute_error(format!(
"attribute '{}' of '{}' objects is not writable",
zelf.name,
zelf.name.as_str(),
obj.class().name()
)))
}
}
#[pygetset]
fn __name__(&self) -> String {
self.name.clone()
}
#[pygetset]
fn __qualname__(&self) -> String {
format!("{}.{}", self.class.slot_name(), self.name.clone())
format!("{}.{}", self.class.slot_name(), self.name.as_str())
}
#[pygetset]
fn __doc__(&self) -> Option<String> {
self.doc.clone()
}
#[pymember]
fn __objclass__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(zelf.class.clone().into())
}
}
impl Representable for PyGetSet {
@@ -165,11 +156,11 @@ impl Representable for PyGetSet {
let class = &zelf.class;
// Special case for object type
if class.is(vm.ctx.types.object_type) {
Ok(format!("<attribute '{}'>", zelf.name))
Ok(format!("<attribute '{}'>", zelf.name.as_str()))
} else {
Ok(format!(
"<attribute '{}' of '{}' objects>",
zelf.name,
zelf.name.as_str(),
class.name()
))
}
+4 -23
View File
@@ -13,15 +13,16 @@ use crate::{
use itertools::Itertools;
use rustpython_common::wtf8::Wtf8Buf;
/// Interpolation object for t-strings (PEP 750).
///
/// Represents an interpolated expression within a template string.
#[pyclass(module = "string.templatelib", name = "Interpolation")]
#[derive(Debug, Clone)]
pub struct PyInterpolation {
#[pymember(type = "object_ex")]
pub value: PyObjectRef,
#[pymember(type = "object_ex")]
pub expression: PyStrRef,
#[pymember(type = "object_ex")]
pub conversion: PyObjectRef, // None or 's', 'r', 'a'
#[pymember(type = "object_ex")]
pub format_spec: PyStrRef,
}
@@ -119,26 +120,6 @@ impl PyInterpolation {
])
}
#[pygetset]
fn value(&self) -> PyObjectRef {
self.value.clone()
}
#[pygetset]
fn expression(&self) -> PyStrRef {
self.expression.clone()
}
#[pygetset]
fn conversion(&self) -> PyObjectRef {
self.conversion.clone()
}
#[pygetset]
fn format_spec(&self) -> PyStrRef {
self.format_spec.clone()
}
#[pyclassmethod]
fn __class_getitem__(
cls: PyTypeRef,
-1
View File
@@ -306,7 +306,6 @@ impl PyList {
}
}
/// Return self[index].
#[pymethod(coexist)]
fn __getitem__(&self, index: PyObjectRef, vm: &VirtualMachine) -> PyResult {
self._getitem(&index, vm)
+1 -1
View File
@@ -98,7 +98,7 @@ pub use zip::PyZip;
#[path = "union.rs"]
pub(crate) mod union_;
pub use union_::{PyUnion, make_union};
pub(crate) mod descriptor;
pub mod descriptor;
pub use descriptor::{
MemberAccess, MemberKind, PY_READONLY, PY_RELATIVE_OFFSET, PyDescriptorOwned,
PyMemberDef as DescriptorMemberDef, PyMemberDescriptor,
+3 -7
View File
@@ -83,6 +83,9 @@ impl PyModuleDef {
}
#[pyclass(module = false, name = "module")]
// The dict lives in the object extension, not the payload. The offset is
// the dict cell at the front of that extension.
#[pymember(name = "__dict__", offset = ::rustpython_vm::object::dict_member_offset())]
#[derive(Debug)]
pub struct PyModule {
// PyObject *md_dict;
@@ -292,13 +295,6 @@ impl PyModule {
Self::new().into_ref_with_type(vm, cls).map(Into::into)
}
#[pymember]
fn __dict__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
zelf.dict()
.map(Into::into)
.ok_or_else(|| vm.new_attribute_error("module has no __dict__"))
}
#[pymethod]
fn __dir__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
// First check if __dict__ attribute exists and is actually a dictionary
-3
View File
@@ -11,9 +11,6 @@ use crate::{
};
use rustpython_common::wtf8::Wtf8Buf;
/// A simple attribute-based namespace.
///
/// SimpleNamespace(**kwargs)
#[pyclass(module = "types", name = "SimpleNamespace")]
#[derive(Debug, Default)]
pub struct PyNamespace {}
+4 -38
View File
@@ -13,13 +13,6 @@ use crate::{
};
use itertools::Itertools;
/// object()
/// --
///
/// The base class of the class hierarchy.
///
/// When called, it accepts no arguments and returns a new featureless
/// instance that has no instance attributes and cannot be given any.
#[pyclass(module = false, name = "object")]
#[derive(Debug)]
pub struct PyBaseObject;
@@ -218,37 +211,10 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine)
type_slot_names(obj.class(), vm).map_err(|_| vm.new_type_error("cannot pickle object"))?;
if required {
// Start with PyBaseObject_Type's basicsize
let mut basicsize = vm.ctx.types.object_type.slots().basicsize;
// Add __dict__ size if type has dict
if obj.class().slots().flags.has_feature(PyTypeFlags::HAS_DICT) {
basicsize += core::mem::size_of::<PyObjectRef>();
}
// Add __weakref__ size if type has weakref support
let has_weakref = if let Some(ext) = obj.class().heaptype_ext() {
match &ext.slots {
None => true, // Heap type without __slots__ has automatic weakref
Some(slots) => slots
.as_slice()
.iter()
.any(|s| s.as_bytes() == b"__weakref__"),
}
} else {
let weakref_name = vm.ctx.intern_str("__weakref__");
obj.class().attributes().contains(weakref_name)
};
if has_weakref {
basicsize += core::mem::size_of::<PyObjectRef>();
}
// Add slots size
if let Some(ref slot_names) = slot_names {
basicsize += core::mem::size_of::<PyObjectRef>() * slot_names.__len__();
}
// Fail if actual type's basicsize > expected basicsize
// Dict, weakref list, and slot cells sit in the prefix in front of
// the payload, so they are not part of `slots.basicsize`. Only state
// stored inside the payload counts.
let basicsize = vm.ctx.types.object_type.slots().basicsize;
if obj.class().slots().basicsize > basicsize {
return Err(vm.new_type_error(format!("cannot pickle '{}' object", obj.class().name())));
}
+29 -58
View File
@@ -3,6 +3,7 @@
*/
use super::PyType;
use crate::common::lock::PyRwLock;
use crate::object::PyAtomicRef;
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
class::PyClassImpl,
@@ -42,10 +43,14 @@ impl FromArgs for SetNameArgs {
#[pyclass(module = false, name = "property", traverse)]
#[derive(Debug)]
pub struct PyProperty {
getter: PyRwLock<Option<PyObjectRef>>,
setter: PyRwLock<Option<PyObjectRef>>,
deleter: PyRwLock<Option<PyObjectRef>>,
doc: PyRwLock<Option<PyObjectRef>>,
#[pymember(name = "fget")]
getter: PyAtomicRef<Option<PyObject>>,
#[pymember(name = "fset")]
setter: PyAtomicRef<Option<PyObject>>,
#[pymember(name = "fdel")]
deleter: PyAtomicRef<Option<PyObject>>,
#[pymember(name = "__doc__", writable)]
doc: PyAtomicRef<Option<PyObject>>,
name: PyRwLock<Option<PyObjectRef>>,
#[pytraverse(skip)]
getter_doc: core::sync::atomic::AtomicBool,
@@ -83,7 +88,7 @@ impl GetDescriptor for PyProperty {
}
// Clone and release lock before calling Python code to prevent deadlock
let value = zelf.getter.read().clone();
let value = zelf.getter.load_owned();
if let Some(getter) = value {
getter.call((obj.to_owned(),), vm)
} else {
@@ -108,7 +113,7 @@ impl PyProperty {
}
// Clone and release lock before calling Python code to prevent deadlock
let Some(getter) = self.getter.read().clone() else {
let Some(getter) = self.getter.load_owned() else {
return Ok(None);
};
@@ -139,7 +144,7 @@ impl PyProperty {
match value {
PySetterValue::Assign(value) => {
// Clone and release lock before calling Python code to prevent deadlock
let set = zelf.setter.read().clone();
let set = zelf.setter.load_owned();
if let Some(setter) = set {
setter.call((obj, value), vm).map(drop)
} else {
@@ -149,7 +154,7 @@ impl PyProperty {
}
PySetterValue::Delete => {
// Clone and release lock before calling Python code to prevent deadlock
let del = zelf.deleter.read().clone();
let del = zelf.deleter.load_owned();
if let Some(deleter) = del {
deleter.call((obj,), vm).map(drop)
} else {
@@ -160,36 +165,16 @@ impl PyProperty {
}
}
// Access functions
#[pymember]
fn fget(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(vm.unwrap_or_none(zelf.getter.read().clone()))
}
pub(crate) fn get_fget(&self) -> Option<PyObjectRef> {
self.getter.read().clone()
}
#[pymember]
fn fset(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(vm.unwrap_or_none(zelf.setter.read().clone()))
self.getter.load_owned()
}
fn get_fset(&self) -> Option<PyObjectRef> {
self.setter.read().clone()
}
#[pymember]
fn fdel(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(vm.unwrap_or_none(zelf.deleter.read().clone()))
self.setter.load_owned()
}
fn get_fdel(&self) -> Option<PyObjectRef> {
self.deleter.read().clone()
self.deleter.load_owned()
}
#[pygetset(name = "__name__")]
@@ -206,10 +191,7 @@ impl PyProperty {
}
fn doc_getter(&self) -> Option<PyObjectRef> {
self.doc.read().clone()
}
fn doc_setter(&self, value: Option<PyObjectRef>) {
*self.doc.write() = value;
self.doc.load_owned()
}
#[pymethod]
@@ -302,21 +284,21 @@ impl PyProperty {
// Clone and release lock before calling Python code to prevent deadlock
// Check getter
if let Some(getter) = self.getter.read().clone()
if let Some(getter) = self.getter.load_owned()
&& is_abstract(&getter)?
{
return Ok(vm.ctx.new_bool(true).into());
}
// Check setter
if let Some(setter) = self.setter.read().clone()
if let Some(setter) = self.setter.load_owned()
&& is_abstract(&setter)?
{
return Ok(vm.ctx.new_bool(true).into());
}
// Check deleter
if let Some(deleter) = self.deleter.read().clone()
if let Some(deleter) = self.deleter.load_owned()
&& is_abstract(&deleter)?
{
return Ok(vm.ctx.new_bool(true).into());
@@ -328,7 +310,7 @@ impl PyProperty {
#[pygetset(setter)]
fn set___isabstractmethod__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
// Clone and release lock before calling Python code to prevent deadlock
let maybe_getter = self.getter.read().clone();
let maybe_getter = self.getter.load_owned();
if let Some(getter) = maybe_getter {
getter.set_attr("__isabstractmethod__", value, vm)?;
}
@@ -368,10 +350,10 @@ impl Constructor for PyProperty {
fn py_new(_cls: &Py<PyType>, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<Self> {
Ok(Self {
getter: PyRwLock::new(None),
setter: PyRwLock::new(None),
deleter: PyRwLock::new(None),
doc: PyRwLock::new(None),
getter: PyAtomicRef::new_empty(),
setter: PyAtomicRef::new_empty(),
deleter: PyAtomicRef::new_empty(),
doc: PyAtomicRef::new_empty(),
name: PyRwLock::new(None),
getter_doc: AtomicBool::new(false),
})
@@ -409,7 +391,7 @@ impl Initializer for PyProperty {
if is_exact_property {
// For exact property type, store doc in the field
*zelf.doc.write() = doc;
zelf.doc.store(doc);
} else {
// For property subclass, set __doc__ as an attribute
let doc_to_set = doc.unwrap_or_else(|| vm.ctx.none());
@@ -423,9 +405,9 @@ impl Initializer for PyProperty {
}
}
*zelf.getter.write() = args.fget;
*zelf.setter.write() = args.fset;
*zelf.deleter.write() = args.fdel;
zelf.getter.store(args.fget);
zelf.setter.store(args.fset);
zelf.deleter.store(args.fdel);
zelf.getter_doc.store(getter_doc, Ordering::Relaxed);
Ok(())
@@ -434,15 +416,4 @@ impl Initializer for PyProperty {
pub(crate) fn init(context: &'static Context) {
PyProperty::extend_class(context, context.types.property_type);
// This is a bit unfortunate, but this instance attribute overlaps with the
// class __doc__ string..
extend_class!(context, context.types.property_type, {
"__doc__" => context.new_static_getset(
"__doc__",
context.types.property_type,
PyProperty::doc_getter,
PyProperty::doc_setter,
),
});
}
+6 -18
View File
@@ -64,8 +64,11 @@ fn iter_search(
#[pyclass(module = false, name = "range")]
#[derive(Debug, Clone)]
pub struct PyRange {
#[pymember(type = "object_ex")]
pub start: PyIntRef,
#[pymember(type = "object_ex")]
pub stop: PyIntRef,
#[pymember(type = "object_ex")]
pub step: PyIntRef,
}
@@ -259,21 +262,6 @@ impl PyRange {
.into_ref_with_type(vm, cls)
}
#[pygetset]
fn start(&self) -> PyIntRef {
self.start.clone()
}
#[pygetset]
fn stop(&self) -> PyIntRef {
self.stop.clone()
}
#[pygetset]
fn step(&self) -> PyIntRef {
self.step.clone()
}
#[pymethod]
fn __reversed__(&self, vm: &VirtualMachine) -> PyObjectRef {
let start = self.start.as_bigint();
@@ -486,14 +474,14 @@ impl Hashable for PyRange {
} else if length.is_one() {
[
vm.ctx.new_int(length).into(),
zelf.start().into(),
zelf.start.clone().into(),
vm.ctx.none(),
]
} else {
[
vm.ctx.new_int(length).into(),
zelf.start().into(),
zelf.step().into(),
zelf.start.clone().into(),
zelf.step.clone().into(),
]
};
tuple_hash(&elements, vm)
+6 -19
View File
@@ -9,7 +9,6 @@ use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
class::{PyClassDef, PyClassImpl},
common::hash::{PyHash, PyUHash},
convert::ToPyObject,
function::{ArgIndex, FuncArgs, OptionalArg, PyComparisonValue},
sliceable::SaturatedSlice,
types::{Comparable, Constructor, Hashable, PyComparisonOp, Representable},
@@ -20,8 +19,11 @@ use num_traits::{One, Signed, Zero};
#[pyclass(module = false, name = "slice", unhashable = true, traverse = "manual")]
#[derive(Debug)]
pub struct PySlice {
#[pymember]
pub start: Option<PyObjectRef>,
#[pymember]
pub stop: PyObjectRef,
#[pymember]
pub step: Option<PyObjectRef>,
}
@@ -91,11 +93,6 @@ impl PyPayload for PySlice {
#[pyclass(with(Comparable, Representable, Hashable))]
impl PySlice {
#[pygetset]
fn start(&self, vm: &VirtualMachine) -> PyObjectRef {
self.start.clone().to_pyobject(vm)
}
pub(crate) fn start_ref<'a>(&'a self, vm: &'a VirtualMachine) -> &'a PyObject {
match &self.start {
Some(v) => v,
@@ -103,16 +100,6 @@ impl PySlice {
}
}
#[pygetset]
pub(crate) fn stop(&self, _vm: &VirtualMachine) -> PyObjectRef {
self.stop.clone()
}
#[pygetset]
fn step(&self, vm: &VirtualMachine) -> PyObjectRef {
self.step.clone().to_pyobject(vm)
}
pub(crate) fn step_ref<'a>(&'a self, vm: &'a VirtualMachine) -> &'a PyObject {
match &self.step {
Some(v) => v,
@@ -158,7 +145,7 @@ impl PySlice {
step = One::one();
} else {
// Clone the value, not the reference.
let this_step = self.step(vm).try_index(vm)?;
let this_step = self.step_ref(vm).try_index(vm)?;
step = this_step.as_bigint().clone();
if step.is_zero() {
@@ -192,7 +179,7 @@ impl PySlice {
lower.clone()
};
} else {
let this_start = self.start(vm).try_index(vm)?;
let this_start = self.start_ref(vm).try_index(vm)?;
start = this_start.as_bigint().clone();
if start < Zero::zero() {
@@ -212,7 +199,7 @@ impl PySlice {
if vm.is_none(&self.stop) {
stop = if backwards { lower } else { upper };
} else {
let this_stop = self.stop(vm).try_index(vm)?;
let this_stop = self.stop.try_index(vm)?;
stop = this_stop.as_bigint().clone();
if stop < Zero::zero() {
+15 -25
View File
@@ -7,15 +7,17 @@ use super::{
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
class::{PyClassDef, PyClassImpl},
common::lock::PyMutex,
function::{FuncArgs, PySetterValue},
object::PyAtomicRef,
types::{Callable, Constructor, GetDescriptor, Initializer, Representable},
};
#[pyclass(module = false, name = "staticmethod", traverse)]
#[derive(Debug)]
pub struct PyStaticMethod {
pub callable: PyMutex<PyObjectRef>,
#[pymember(name = "__func__")]
#[pymember(name = "__wrapped__")]
pub callable: PyAtomicRef<PyObject>,
}
impl PyPayload for PyStaticMethod {
@@ -33,14 +35,14 @@ impl GetDescriptor for PyStaticMethod {
vm: &VirtualMachine,
) -> PyResult {
let (zelf, _obj) = Self::_unwrap(zelf, obj, vm)?;
Ok(zelf.callable.lock().clone())
Ok(zelf.callable.load_owned())
}
}
impl From<PyObjectRef> for PyStaticMethod {
fn from(callable: PyObjectRef) -> Self {
Self {
callable: PyMutex::new(callable),
callable: PyAtomicRef::from(callable),
}
}
}
@@ -61,7 +63,7 @@ impl Constructor for PyStaticMethod {
// `None`, matching CPython.
let _: StaticMethodArgs = args.bind_for(vm, Self::NAME)?;
let result = Self {
callable: PyMutex::new(vm.ctx.none()),
callable: PyAtomicRef::from(vm.ctx.none()),
}
.into_ref_with_type(vm, cls)?;
Ok(PyObjectRef::from(result))
@@ -76,7 +78,7 @@ impl PyStaticMethod {
#[must_use]
pub fn new(callable: PyObjectRef) -> Self {
Self {
callable: PyMutex::new(callable),
callable: PyAtomicRef::from(callable),
}
}
@@ -91,7 +93,7 @@ impl Initializer for PyStaticMethod {
fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
let callable = args.function;
*zelf.callable.lock() = callable.clone();
zelf.callable.store(callable.clone());
functools_wraps(zelf.as_object(), &callable, vm)
}
}
@@ -101,21 +103,9 @@ impl Initializer for PyStaticMethod {
flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
)]
impl PyStaticMethod {
#[pymember]
fn __func__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(zelf.callable.lock().clone())
}
#[pymember]
fn __wrapped__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(zelf.callable.lock().clone())
}
#[pygetset]
fn __annotations__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
let callable = zelf.callable.lock();
let callable = zelf.callable.load_owned();
descriptor_get_wrapped_attribute(
&callable,
zelf.as_object(),
@@ -141,7 +131,7 @@ impl PyStaticMethod {
#[pygetset]
fn __annotate__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
let callable = zelf.callable.lock();
let callable = zelf.callable.load_owned();
descriptor_get_wrapped_attribute(
&callable,
zelf.as_object(),
@@ -167,7 +157,7 @@ impl PyStaticMethod {
#[pygetset]
fn __isabstractmethod__(&self, vm: &VirtualMachine) -> PyObjectRef {
let callable = self.callable.lock().clone();
let callable = self.callable.load_owned();
if let Ok(Some(is_abstract)) = vm.get_attribute_opt(&callable, "__isabstractmethod__") {
is_abstract
@@ -179,7 +169,7 @@ impl PyStaticMethod {
#[pygetset(setter)]
fn set___isabstractmethod__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
self.callable
.lock()
.load_owned()
.set_attr("__isabstractmethod__", value, vm)?;
Ok(())
}
@@ -198,14 +188,14 @@ impl Callable for PyStaticMethod {
type Args = FuncArgs;
#[inline]
fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
let callable = zelf.callable.lock().clone();
let callable = zelf.callable.load_owned();
callable.call(args, vm)
}
}
impl Representable for PyStaticMethod {
fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
let callable = zelf.callable.lock().repr(vm)?;
let callable = zelf.callable.load_owned().repr(vm)?;
let class = Self::class(&vm.ctx);
match (
+2 -14
View File
@@ -16,13 +16,12 @@ use crate::{
};
use rustpython_common::wtf8::{Wtf8Buf, wtf8_concat};
/// Template object for t-strings (PEP 750).
///
/// Represents a template string with interpolated expressions.
#[pyclass(module = "string.templatelib", name = "Template")]
#[derive(Debug, Clone)]
pub struct PyTemplate {
#[pymember(type = "object_ex")]
pub strings: PyTupleRef,
#[pymember(type = "object_ex")]
pub interpolations: PyTupleRef,
}
@@ -98,16 +97,6 @@ impl Constructor for PyTemplate {
#[pyclass(with(Constructor, Comparable, Iterable, Representable, AsSequence))]
impl PyTemplate {
#[pygetset]
fn strings(&self) -> PyTupleRef {
self.strings.clone()
}
#[pygetset]
fn interpolations(&self) -> PyTupleRef {
self.interpolations.clone()
}
#[pygetset]
fn values(&self, vm: &VirtualMachine) -> PyTupleRef {
let values: Vec<PyObjectRef> = self
@@ -271,7 +260,6 @@ impl Representable for PyTemplate {
}
}
/// Iterator for Template objects
#[pyclass(module = "string.templatelib", name = "TemplateIter")]
#[derive(Debug)]
pub struct PyTemplateIter {
+12 -19
View File
@@ -46,7 +46,8 @@ pub(crate) type PyTypeTupleRef = PyRef<PyTuple<PyTypeRef>>;
#[pyclass(module = false, name = "type", traverse = "manual")]
pub struct PyType {
/// tp_base. Written under the type lock (see `set_bases`); read lock-free.
// tp_base. Written under the type lock (see `set_bases`); read lock-free.
#[pymember(name = "__base__", doc = false)]
pub base: PyAtomicRef<Option<Self>>,
pub bases: PyRwLock<PyTypeTupleRef>,
pub mro: PyRwLock<Vec<PyTypeRef>>,
@@ -329,7 +330,7 @@ impl TypeSpecializationCache {
new.as_object().mark_cache_published();
}
// SAFETY: reclamation of published objects is deferred via QSBR;
// racing try_to_owned readers never touch freed memory.
// racing load_owned readers never touch freed memory.
let old = unsafe { self.init.swap(new_init) };
if let Some(old) = old {
// Dropping may run arbitrary Python; defer past the type lock.
@@ -1398,10 +1399,7 @@ impl PyType {
return None;
}
// Check order: pointer (Acquire) then function version.
let init = ext
.specialization_cache
.init
.try_to_owned(Ordering::Acquire)?;
let init = ext.specialization_cache.init.load_owned()?;
let cached_version = ext
.specialization_cache
.init_version
@@ -1446,10 +1444,7 @@ impl PyType {
pub(crate) fn get_cached_getitem_for_specialization(&self) -> Option<(PyRef<PyFunction>, u32)> {
let ext = self.heaptype_ext.as_ref()?;
// Check order: pointer (Acquire) then function version.
let getitem = ext
.specialization_cache
.getitem
.try_to_owned(Ordering::Acquire)?;
let getitem = ext.specialization_cache.getitem.load_owned()?;
let cached_version = ext
.specialization_cache
.getitem_version
@@ -1775,7 +1770,10 @@ impl PyType {
// Reject reparenting onto a base whose instances have an incompatible
// object layout.
let old_base = zelf.base.deref().unwrap_or(vm.ctx.types.object_type);
let old_base_owned = zelf.base.load_owned();
let old_base = old_base_owned
.as_deref()
.unwrap_or(vm.ctx.types.object_type);
compatible_for_assignment(old_base, &new_base, "__bases__", vm)?;
// References released inside the critical section are collected here
@@ -1922,11 +1920,6 @@ impl PyType {
result
}
#[pygetset]
fn __base__(&self) -> Option<PyTypeRef> {
self.base.to_owned()
}
#[pygetset]
fn __flags__(&self) -> u64 {
self.slots.flags.bits()
@@ -2831,7 +2824,7 @@ impl Constructor for PyType {
qualname: PyRwLock::new(None),
},
member: member_def,
access: MemberAccess::Slot,
access: MemberAccess::Offset,
});
// __slots__ attributes always get a member descriptor
// (this overrides any inherited attribute from MRO)
@@ -3329,7 +3322,7 @@ fn get_builtin_base_with_dict(typ: &Py<PyType>, vm: &VirtualMachine) -> Option<P
{
return Some(t);
}
current = t.__base__();
current = t.base.load_owned();
}
None
}
@@ -3491,7 +3484,7 @@ pub(crate) fn call_slot_new(
.load()
.is_some_and(|f| fn_addr(f) == fn_addr(new_wrapper as NewFunc))
{
match staticbase.base.to_owned() {
match staticbase.base.load_owned() {
Some(base) => staticbase = base,
None => break,
}
+3 -7
View File
@@ -18,6 +18,7 @@ const CLS_ATTRS: &[&str] = &["__module__"];
#[pyclass(module = "typing", name = "Union", traverse)]
pub struct PyUnion {
#[pymember(name = "__args__")]
args: PyTupleRef,
/// Frozenset of hashable args, or None if all args were hashable
hashable_args: Option<PyRef<PyFrozenSet>>,
@@ -124,11 +125,6 @@ impl PyUnion {
self.parameters.clone().into()
}
#[pygetset]
fn __args__(&self) -> PyObjectRef {
self.args.clone().into()
}
#[pymethod]
fn __instancecheck__(
zelf: PyRef<Self>,
@@ -143,7 +139,7 @@ impl PyUnion {
{
Err(vm.new_type_error("isinstance() argument 2 cannot be a parameterized generic"))
} else {
obj.is_instance(zelf.__args__().as_object(), vm)
obj.is_instance(zelf.args.as_object(), vm)
}
}
@@ -161,7 +157,7 @@ impl PyUnion {
{
Err(vm.new_type_error("issubclass() argument 2 cannot be a parameterized generic"))
} else {
obj.is_subclass(zelf.__args__().as_object(), vm)
obj.is_subclass(zelf.args.as_object(), vm)
}
}
+27 -2
View File
@@ -140,6 +140,9 @@ pub trait PyClassDef {
const BASICSIZE: usize;
const ITEMSIZE: usize = 0;
const UNHASHABLE: bool = false;
const MEMBERS: &'static [crate::builtins::descriptor::PyMemberSpec] = &[];
fn assert_member_layout() {}
// due to restriction of rust trait system, object.__base__ is None
// but PyBaseObject::Base will be PyBaseObject.
@@ -240,6 +243,22 @@ pub trait PyClassImpl: PyClassDef {
);
}
Self::assert_member_layout();
for member in Self::MEMBERS {
class.set_str_attr(
member.name,
ctx.new_member(
member.name,
member.kind,
member.offset,
member.flags,
class,
member.doc,
),
ctx,
);
}
Self::impl_extend_class(ctx, class);
if let Some(doc) = Self::DOC {
@@ -284,8 +303,14 @@ pub trait PyClassImpl: PyClassDef {
// Add slot wrappers using SLOT_DEFS array
add_operators::<Self>(class, ctx);
// Inherit slots from base types after slots are fully initialized
for base in class.bases.read().as_slice() {
// Same walk as init_slots: a slot such as tp_init is copied only from
// a base that defines it, so a static grandchild must see that base
// in the MRO, not only its direct bases.
let mro = {
let guard = class.mro.read();
guard[1..].to_vec()
};
for base in &mro {
class.inherit_slots(base);
}
+1 -2
View File
@@ -9,7 +9,6 @@ use crate::{
protocol::PyIterReturn,
vm::GenFrameLink,
};
use core::sync::atomic::Ordering;
use crossbeam_utils::atomic::AtomicCell;
impl ExecutionResult {
@@ -522,7 +521,7 @@ impl Coro {
}
pub fn frame_opt(&self) -> Option<FrameObjectRef> {
self.frame.try_to_owned(Ordering::Acquire)
self.frame.load_owned()
}
pub fn code(&self) -> PyRef<PyCode> {
+10 -14
View File
@@ -53,7 +53,9 @@ pub(super) mod types {
#[repr(C)]
pub struct PyBaseExceptionGroup {
base: PyBaseException,
#[pymember(name = "message")]
msg: PyAtomicRef<PyObject>,
#[pymember(name = "exceptions")]
excs: PyAtomicRef<PyObject>,
excs_str: PyAtomicRef<Option<PyObject>>,
}
@@ -85,16 +87,6 @@ pub(super) mod types {
#[pyexception(with(Constructor, Initializer))]
impl PyBaseExceptionGroup {
#[pygetset]
fn message(&self) -> PyObjectRef {
self.msg.to_owned()
}
#[pygetset]
fn exceptions(&self) -> PyObjectRef {
self.excs.to_owned()
}
#[pyclassmethod]
fn __class_getitem__(
cls: PyTypeRef,
@@ -228,8 +220,11 @@ pub(super) mod types {
Ok(vm.ctx.new_tuple(vec![match_group, rest_group]))
}
#[pymethod]
fn __str__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
#[pyslot]
fn slot_str(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
let zelf: &Py<Self> = zelf
.downcast_ref()
.expect("slot wrapper checked BaseExceptionGroup");
let message = zelf.msg.str(vm)?;
let num_excs = zelf
.excs
@@ -238,7 +233,8 @@ pub(super) mod types {
let suffix = if num_excs == 1 { "" } else { "s" };
let mut result = message.as_wtf8().to_owned();
write!(result, " ({num_excs} sub-exception{suffix})").unwrap();
write!(result, " ({num_excs} sub-exception{suffix})")
.expect("formatting into a string buffer cannot fail");
Ok(vm.ctx.new_str(result))
}
@@ -250,7 +246,7 @@ pub(super) mod types {
let class_name = zelf.class().name().to_owned();
let message = zelf.msg.repr(vm)?;
let exceptions_str = if let Some(saved) = zelf.excs_str.to_owned() {
let exceptions_str = if let Some(saved) = zelf.excs_str.load_owned() {
saved
.downcast::<crate::builtins::PyStr>()
.map_err(|_| vm.new_type_error("__repr__ returned non-string"))?
File diff suppressed because it is too large. Load diff
+27 -10
View File
@@ -874,7 +874,6 @@ unsafe impl Traverse for FrameLocals {
/// to keep the hot InterpreterFrame small.
pub(crate) struct FrameColdData {
pub trace: PyMutex<Option<PyObjectRef>>,
pub trace_lines: PyMutex<bool>,
pub trace_opcodes: PyMutex<bool>,
pub temporary_refs: PyMutex<Vec<PyObjectRef>>,
pub f_extra_locals: PyMutex<Option<PyDictRef>>,
@@ -897,7 +896,6 @@ impl Default for FrameColdData {
fn default() -> Self {
Self {
trace: PyMutex::new(None),
trace_lines: PyMutex::new(true),
trace_opcodes: PyMutex::new(false),
temporary_refs: PyMutex::new(Vec::new()),
f_extra_locals: PyMutex::new(None),
@@ -1320,6 +1318,7 @@ impl InterpreterFrame {
};
let frame_obj = FrameObject {
f_trace_lines: core::sync::atomic::AtomicBool::new(self.trace_lines_flag()),
owned_code: Some(code),
owned_globals: Some(globals),
owned_builtins: Some(builtins),
@@ -1397,6 +1396,7 @@ impl InterpreterFrame {
};
let frame_obj = FrameObject {
f_trace_lines: core::sync::atomic::AtomicBool::new(self.trace_lines_flag()),
owned_code: Some(code),
owned_globals: Some(globals),
owned_builtins: Some(builtins),
@@ -1492,6 +1492,20 @@ impl InterpreterFrame {
self.cold.get().map(|b| &**b)
}
/// `f_trace_lines` of the materialized frame object. True when the frame
/// has not been materialized.
pub(crate) fn trace_lines_flag(&self) -> bool {
let mat = self.materialized.load(atomic::Ordering::Relaxed);
if mat != 0 {
// SAFETY: `materialized` holds a `Py<FrameObject>` that stays
// allocated while the pointer is published.
return unsafe { &*(mat as *const Py<FrameObject>) }
.f_trace_lines
.load(core::sync::atomic::Ordering::Relaxed);
}
true
}
/// Thread still running the frame this one was materialized from, or 0.
#[inline]
pub(crate) fn attached_tid(&self) -> u64 {
@@ -1513,6 +1527,9 @@ impl InterpreterFrame {
/// Analogous to CPython's `PyFrameObject`.
#[pyclass(module = false, name = "frame", traverse = "manual")]
pub struct FrameObject {
// The executing iframe reads this when it points at the frame object.
#[pymember(name = "f_trace_lines", writable)]
pub(crate) f_trace_lines: core::sync::atomic::AtomicBool,
// Owned references — keep the pointed-to objects alive for InterpreterFrame's
// raw pointers. Wrapped in Option so Traverse::clear can release them,
// allowing GC cycle collection to reclaim referenced objects.
@@ -1821,6 +1838,7 @@ impl FrameObject {
)
};
Self {
f_trace_lines: core::sync::atomic::AtomicBool::new(true),
owned_code: Some(code),
owned_globals: Some(scope.globals),
owned_builtins: Some(builtins),
@@ -3353,12 +3371,10 @@ impl ExecutingFrame<'_> {
.is_some_and(|c| *c.trace_opcodes.lock())
}
/// f_trace_lines, defaulting to true when cold data is not allocated.
/// f_trace_lines, defaulting to true when no frame object exists.
#[inline]
fn trace_lines_is_set(&self) -> bool {
self.iframe()
.cold_opt()
.is_none_or(|c| *c.trace_lines.lock())
self.iframe().trace_lines_flag()
}
/// Get pending_stack_pops from the frame.
@@ -7035,7 +7051,7 @@ impl ExecutingFrame<'_> {
.localsplus
.stack_index(self_index)
.as_ref()
.is_some_and(|self_obj| self_obj.class().is(descr.objclass))
.is_some_and(|self_obj| self_obj.class().is(descr.common.typ))
{
let func = descr.method.func;
let callee = Callee::named(descr.method.name).with_instance_arg(true);
@@ -7076,7 +7092,7 @@ impl ExecutingFrame<'_> {
.localsplus
.stack_index(self_index)
.as_ref()
.is_some_and(|self_obj| self_obj.class().is(descr.objclass))
.is_some_and(|self_obj| self_obj.class().is(descr.common.typ))
{
let func = descr.method.func;
let callee = Callee::named(descr.method.name).with_instance_arg(true);
@@ -7117,7 +7133,7 @@ impl ExecutingFrame<'_> {
.localsplus
.stack_index(self_index)
.as_ref()
.is_some_and(|self_obj| self_obj.class().is(descr.objclass))
.is_some_and(|self_obj| self_obj.class().is(descr.common.typ))
{
let func = descr.method.func;
let callee = Callee::named(descr.method.name).with_instance_arg(true);
@@ -7223,7 +7239,7 @@ impl ExecutingFrame<'_> {
.localsplus
.stack_index(self_index)
.as_ref()
.is_some_and(|self_obj| self_obj.class().is(descr.objclass))
.is_some_and(|self_obj| self_obj.class().is(descr.common.typ))
{
let func = descr.method.func;
let callee = Callee::named(descr.method.name).with_instance_arg(true);
@@ -10212,6 +10228,7 @@ impl ExecutingFrame<'_> {
if let Some(ref descr) = cls_attr
&& let Some(member_descr) = descr.downcast_ref::<PyMemberDescriptor>()
&& let Some(offset) = member_descr.slot_offset()
&& !member_descr.member.audit_read()
&& cls.fast_issubclass(&member_descr.common.typ)
{
unsafe {
+94 -43
View File
@@ -239,9 +239,13 @@ pub(super) unsafe fn default_dealloc<T: PyPayload>(obj: *mut PyObject) {
&& !obj_ref.0.ref_count.is_published()
{
if let Some(ext) = obj_ref.0.ext_ref() {
if let Some(dict) = &ext.dict {
let old = dict.d.write().take();
drop(old);
if obj_ref
.class()
.slots
.flags
.has_feature(crate::types::PyTypeFlags::HAS_DICT)
{
drop(ext.dict.replace(None));
}
for slot in obj_ref.0.slot_cells() {
drop(slot.store(None));
@@ -365,16 +369,19 @@ unsafe impl Link for GcLink {
/// but some payloads like PyWeak have align 8 due to i64 fields).
#[repr(C, align(8))]
pub(super) struct ObjExt {
pub(super) dict: Option<InstanceDict>,
/// Always present. The dict cell is its first field, so
/// `dict_member_offset` addresses that pointer. Null when the type has
/// no dict slot.
pub(super) dict: InstanceDict,
}
impl ObjExt {
fn new(dict: Option<PyDictRef>, has_dict: bool, inline_values: bool) -> Self {
Self {
dict: if has_dict {
Some(InstanceDict::from_opt(dict, inline_values))
InstanceDict::from_opt(dict, inline_values)
} else {
None
InstanceDict::from_opt(None, false)
},
}
}
@@ -400,7 +407,7 @@ const EXT_OFFSET: usize = core::mem::size_of::<ObjExt>();
pub(crate) fn slot_member_offset(index: usize) -> isize {
// Cell 0 sits directly in front of ObjExt. Higher indexes extend further
// forward, so a base class offset stays valid on a subclass with more slots.
let cell = core::mem::size_of::<PyAtomicRef<PyObject>>();
let cell = core::mem::size_of::<PyAtomicRef<Option<PyObject>>>();
-((EXT_OFFSET + (index + 1) * cell) as isize)
}
@@ -408,7 +415,7 @@ fn slot_region_layout(member_count: usize) -> Option<core::alloc::Layout> {
if member_count == 0 {
return None;
}
let cell = core::mem::size_of::<PyAtomicRef<PyObject>>();
let cell = core::mem::size_of::<PyAtomicRef<Option<PyObject>>>();
let bytes = member_count * cell;
let align = core::mem::align_of::<ObjExt>();
// Padding goes in front of the cells so cell 0 stays flush with ObjExt.
@@ -465,6 +472,23 @@ pub struct Py<T> {
}
pub const SIZEOF_PYOBJECT_HEAD: usize = core::mem::size_of::<Py<()>>();
/// Byte offset of the payload inside `Py<T>`.
#[must_use]
#[inline]
pub const fn payload_offset<T>() -> usize {
core::mem::offset_of!(Py<T>, payload)
}
/// Byte offset of the instance-dict pointer from the start of `Py`.
///
/// The pointer is the first field of `ObjExt`, which sits immediately in front
/// of `Py`. The cell owns the dict.
#[must_use]
#[inline]
pub const fn dict_member_offset() -> isize {
-(core::mem::size_of::<ObjExt>() as isize)
}
// ref_count, vtable, gc_pointers (two) and typ are one word each; the gc bits,
// generation, owner and refs take eight bytes between them. A 64-bit header had
// those eight as the padding its alignment forces, so they cost it nothing; a
@@ -514,8 +538,8 @@ impl<T> Py<T> {
}
/// Member cells are the pointer array immediately before [`ObjExt`].
/// Layout: `[WeakRefList?][PyAtomicRef<PyObject>; N][ObjExt?][Py]`.
pub(super) fn slot_cells(&self) -> &[PyAtomicRef<PyObject>] {
/// Layout: `[WeakRefList?][PyAtomicRef<Option<PyObject>>; N][ObjExt?][Py]`.
pub(super) fn slot_cells(&self) -> &[PyAtomicRef<Option<PyObject>>] {
let Some(ext) = self.ext_ref() else {
return &[];
};
@@ -523,12 +547,12 @@ impl<T> Py<T> {
if member_count == 0 {
return &[];
}
let cell = core::mem::size_of::<PyAtomicRef<PyObject>>();
let cell = core::mem::size_of::<PyAtomicRef<Option<PyObject>>>();
// Index 0 is the cell adjacent to ObjExt; index i is i cells before it.
let first = (ext as *const ObjExt)
.addr()
.wrapping_sub(member_count * cell);
let ptr = core::ptr::with_exposed_provenance::<PyAtomicRef<PyObject>>(first);
let ptr = core::ptr::with_exposed_provenance::<PyAtomicRef<Option<PyObject>>>(first);
unsafe { core::slice::from_raw_parts(ptr, member_count) }
}
@@ -1108,11 +1132,19 @@ impl Py<PyWeak> {
pub(crate) const SHARED_KEYS_MAX_SIZE: usize = 30;
#[derive(Debug)]
#[repr(C)]
pub(crate) struct InstanceDict {
pub(crate) d: PyRwLock<Option<PyDictRef>>,
/// Owned dict pointer. First field so `dict_member_offset` addresses it.
/// Null when this object has no dict.
pub(crate) dict: PyAtomicRef<Option<crate::builtins::PyDict>>,
/// Separate from the pointer: shared-key inline values are valid only
/// while this stays true. Replacing the dict does not change it.
inline_values_valid: PyAtomic<bool>,
}
const _: () = assert!(core::mem::offset_of!(InstanceDict, dict) == 0);
const _: () = assert!(core::mem::offset_of!(ObjExt, dict) == 0);
impl From<PyDictRef> for InstanceDict {
#[inline(always)]
fn from(d: PyDictRef) -> Self {
@@ -1129,7 +1161,7 @@ impl InstanceDict {
#[inline]
pub(crate) fn from_opt(d: Option<PyDictRef>, inline_values: bool) -> Self {
Self {
d: PyRwLock::new(d),
dict: PyAtomicRef::from(d),
inline_values_valid: Radium::new(inline_values),
}
}
@@ -1156,17 +1188,18 @@ impl InstanceDict {
#[inline]
pub(crate) fn get(&self) -> Option<PyDictRef> {
self.d.read().clone()
self.dict.load_owned()
}
/// Run `f` on the dict without cloning it.
/// Run `f` on the current dict.
///
/// For callers that only need to look at the dict — a predicate, a version
/// stamp — this drops the refcount round-trip [`Self::get`] pays. `f` runs
/// under the read guard, so it must not run Python or take this lock again.
/// The dict is held by a strong reference for the call, so a concurrent
/// replace cannot free it. `f` must not re-enter `get_or_insert` on this
/// cell.
#[inline]
pub(crate) fn with<R>(&self, f: impl FnOnce(Option<&Py<crate::builtins::PyDict>>) -> R) -> R {
f(self.d.read().as_deref())
let owned = self.get();
f(owned.as_deref())
}
#[inline]
@@ -1176,20 +1209,22 @@ impl InstanceDict {
#[inline]
pub(crate) fn replace(&self, d: Option<PyDictRef>) -> Option<PyDictRef> {
core::mem::replace(&mut self.d.write(), d)
self.dict.store(d)
}
pub(crate) fn get_or_insert(&self, vm: &VirtualMachine) -> PyDictRef {
if let Some(existing) = self.d.read().as_ref() {
return existing.clone();
}
let dict = vm.ctx.new_dict();
let mut d = self.d.write();
if let Some(existing) = d.as_ref() {
existing.clone()
} else {
*d = Some(dict.clone());
dict
loop {
if let Some(existing) = self.get() {
return existing;
}
let dict = vm.ctx.new_dict();
match self.dict.compare_exchange_empty(dict.clone()) {
Ok(()) => return dict,
Err(rejected) => {
drop(rejected);
drop(dict);
}
}
}
}
}
@@ -1210,7 +1245,7 @@ impl<T: PyPayload> Py<T> {
}
/// Deallocate a Py, handling optional prefix(es).
/// Layout: `[WeakRefList?][PyAtomicRef<PyObject>; N][ObjExt?][Py<T>]`
/// Layout: `[WeakRefList?][PyAtomicRef<Option<PyObject>>; N][ObjExt?][Py<T>]`
///
/// # Safety
/// `ptr` must be a valid pointer from `Py::new` and must not be used after this call.
@@ -1257,9 +1292,9 @@ impl<T: PyPayload> Py<T> {
cursor = cursor.add(core::mem::size_of::<WeakRefList>());
}
if let Some(region) = slot_region_layout(member_count) {
let cell = core::mem::size_of::<PyAtomicRef<PyObject>>();
let cell = core::mem::size_of::<PyAtomicRef<Option<PyObject>>>();
let first = cursor.add(region.size() - member_count * cell);
let cells = first.cast::<PyAtomicRef<PyObject>>();
let cells = first.cast::<PyAtomicRef<Option<PyObject>>>();
for i in 0..member_count {
core::ptr::drop_in_place(cells.add(i));
}
@@ -1292,7 +1327,7 @@ impl<T: PyPayload> Py<T> {
impl<T: PyPayload + core::fmt::Debug> Py<T> {
/// Allocate a new Py, optionally with prefix(es).
/// Returns a raw pointer to the Py (NOT the allocation start).
/// Layout: `[WeakRefList?][PyAtomicRef<PyObject>; N][ObjExt?][Py<T>]`
/// Layout: `[WeakRefList?][PyAtomicRef<Option<PyObject>>; N][ObjExt?][Py<T>]`
fn new(payload: T, typ: PyTypeRef, dict: Option<PyDictRef>) -> *mut Self {
let member_count = typ.slots.member_count;
let needs_ext = typ
@@ -1355,11 +1390,13 @@ impl<T: PyPayload + core::fmt::Debug> Py<T> {
unsafe {
if let Some(offset) = slots_start {
let region = slot_region_layout(member_count).unwrap();
let cell = core::mem::size_of::<PyAtomicRef<PyObject>>();
let cell = core::mem::size_of::<PyAtomicRef<Option<PyObject>>>();
let first = alloc_ptr.add(offset + region.size() - member_count * cell);
let cells = first.cast::<PyAtomicRef<PyObject>>();
let cells = first.cast::<PyAtomicRef<Option<PyObject>>>();
for i in 0..member_count {
cells.add(i).write(PyAtomicRef::<PyObject>::new_empty());
cells
.add(i)
.write(PyAtomicRef::<Option<PyObject>>::new_empty());
}
}
@@ -1778,7 +1815,13 @@ impl PyObject {
#[inline(always)]
pub(crate) fn instance_dict(&self) -> Option<&InstanceDict> {
self.0.ext_ref().and_then(|ext| ext.dict.as_ref())
let ext = self.0.ext_ref()?;
let (flags, _) = self.0.read_type_flags();
if flags.has_feature(crate::types::PyTypeFlags::HAS_DICT) {
Some(&ext.dict)
} else {
None
}
}
/// `_PyObject_InlineValues(obj)->valid` when the type has INLINE_VALUES.
@@ -2180,11 +2223,13 @@ impl PyObject {
drop(self.slot_cell_at(byte_offset).store(value));
}
fn slot_cell_at(&self, byte_offset: isize) -> &PyAtomicRef<Self> {
fn slot_cell_at(&self, byte_offset: isize) -> &PyAtomicRef<Option<Self>> {
let addr = (self as *const Self as *const u8)
.addr()
.wrapping_add(byte_offset as usize);
let ptr = core::ptr::with_exposed_provenance::<PyAtomicRef<Self>>(addr);
let ptr = core::ptr::with_exposed_provenance::<PyAtomicRef<Option<Self>>>(addr);
// SAFETY: `byte_offset` addresses an object-pointer cell. Nullable and
// non-null cells share this layout; member loads go through the nullable view.
unsafe { &*ptr }
}
@@ -2302,7 +2347,9 @@ impl PyObject {
self_addr.wrapping_sub(EXT_OFFSET),
);
let ext = unsafe { &mut *ext_ptr };
if let Some(dict_ref) = ext.dict.as_ref().and_then(|d| d.replace(None)) {
if flags.has_feature(crate::types::PyTypeFlags::HAS_DICT)
&& let Some(dict_ref) = ext.dict.replace(None)
{
result.push(dict_ref.into());
}
for slot in obj.0.slot_cells() {
@@ -2333,7 +2380,11 @@ impl PyObject {
// Py_TPFLAGS_HAVE_GC types have tp_clear
pub fn gc_has_clear(&self) -> bool {
self.0.vtable.clear.is_some()
|| self.0.ext_ref().is_some_and(|ext| ext.dict.is_some())
|| self
.0
.read_type_flags()
.0
.has_feature(crate::types::PyTypeFlags::HAS_DICT)
|| self.0.read_type_flags().1 > 0
}
}
+192 -112
View File
@@ -239,6 +239,21 @@ pub struct PyAtomicRef<T> {
_phantom: PhantomData<T>,
}
// The cell stores a pointer, not an inline `T`. `PhantomData<T>` would
// otherwise make `PyAtomicRef<PyObject>` `!Unpin` because `PyObject` is pinned.
impl<T> Unpin for PyAtomicRef<T> {}
// Typed and untyped cells are the same pointer-sized slot. A typed nullable
// CAS forwards to the untyped one through this layout.
const _: () = assert!(
core::mem::size_of::<PyAtomicRef<Option<PyObject>>>()
== core::mem::size_of::<PyAtomicRef<()>>()
&& core::mem::align_of::<PyAtomicRef<Option<PyObject>>>()
== core::mem::align_of::<PyAtomicRef<()>>()
&& core::mem::offset_of!(PyAtomicRef<Option<PyObject>>, inner)
== core::mem::offset_of!(PyAtomicRef<()>, inner)
);
impl<T> Drop for PyAtomicRef<T> {
fn drop(&mut self) {
// SAFETY: We are dropping the atomic reference, so we can safely
@@ -389,43 +404,6 @@ impl<T: PyPayload> PyAtomicRef<Option<T>> {
unsafe { self.inner.load(ordering).cast::<Py<T>>().as_ref() }
}
pub fn to_owned(&self) -> Option<PyRef<T>> {
self.to_owned_ordering(Ordering::Relaxed)
}
pub fn to_owned_ordering(&self, ordering: Ordering) -> Option<PyRef<T>> {
self.deref_ordering(ordering).map(|x| x.to_owned())
}
/// Try-incref read of the current value.
///
/// Unlike [`Self::to_owned`], this never increfs a destructed object:
/// it uses a conditional incref and revalidates that the slot still
/// holds the same pointer. Returns `None` when the slot is empty.
///
/// Soundness relies on published-object memory being reclaimed only
/// after a QSBR grace period (see `object::qsbr`), so the refcount
/// word of a concurrently swapped-out value stays readable.
pub fn try_to_owned(&self, ordering: Ordering) -> Option<PyRef<T>> {
loop {
let ptr = self.inner.load(ordering);
if ptr.is_null() {
return None;
}
if let Some(obj) = unsafe { PyObject::try_to_owned_from_ptr(ptr.cast::<PyObject>()) } {
if core::ptr::eq(self.inner.load(Ordering::Acquire), ptr) {
// SAFETY: the slot only ever stores `PyRef<T>` values.
return Some(unsafe { obj.downcast_unchecked::<T>() });
}
drop(obj);
}
// Slot changed or the value was torn down mid-read; a failed
// incref with an unchanged slot is impossible (the slot's own
// strong ref keeps the value alive), so this loop progresses.
core::hint::spin_loop();
}
}
/// # Safety
/// The caller is responsible to keep the returned PyRef alive
/// until no more reference can be used via PyAtomicRef::deref()
@@ -444,81 +422,140 @@ impl<T: PyPayload> PyAtomicRef<Option<T>> {
frame.iframe().cold().temporary_refs.lock().push(old.into());
}
}
}
impl PyAtomicRef<PyObject> {
/// Empty slot. The pointer is null and owns no reference.
pub(crate) fn new_empty() -> Self {
Self {
inner: Radium::new(null_mut()),
_phantom: PhantomData,
}
}
/// Borrowed pointer currently stored. Null when the slot is empty.
/// Strong reference to the current value, or `None` when the slot is empty.
///
/// The slot owns the reference, so this stays valid while the slot is
/// unchanged. Traversal calls it with other threads stopped.
pub(crate) fn load_ptr(&self) -> *mut PyObject {
self.inner.load(Ordering::Acquire).cast()
}
/// Strong reference to the current value.
///
/// A concurrent store may drop the previous value. The incref is retried
/// until it applies to the pointer still in the slot, and a value placed
/// here is published so its memory outlives that race.
pub(crate) fn load_owned(&self) -> Option<PyObjectRef> {
let ptr = self.inner.load(Ordering::Acquire);
if ptr.is_null() {
return None;
}
// Without threading the slot's own reference keeps the object alive,
// so one incref is enough. With threading, retry when a store retires
// the pointer between the load and the incref.
#[cfg(not(feature = "threading"))]
{
unsafe { PyObject::try_to_owned_from_ptr(ptr.cast()) }
}
#[cfg(feature = "threading")]
{
let mut ptr = ptr;
loop {
if let Some(obj) = unsafe { PyObject::try_to_owned_from_ptr(ptr.cast()) }
&& core::ptr::eq(self.inner.load(Ordering::Acquire), ptr)
{
return Some(obj);
}
ptr = self.inner.load(Ordering::Acquire);
if ptr.is_null() {
return None;
}
core::hint::spin_loop();
}
}
/// This is the owned read for a nullable cell. A concurrent store may drop
/// the previous value; the incref is retried until it applies to the
/// pointer still in the slot. Published-object memory is reclaimed only
/// after a QSBR grace period (see `object::qsbr`), so the refcount word of
/// a swapped-out value stays readable.
pub fn load_owned(&self) -> Option<PyRef<T>> {
cell_load_owned(&self.inner).map(|obj| {
// SAFETY: a typed cell only stores references of payload `T`.
unsafe { obj.downcast_unchecked() }
})
}
/// Replace the stored reference. Returns the previous one, still owned.
pub(crate) fn store(&self, value: Option<PyRef<T>>) -> Option<PyRef<T>> {
cell_store(&self.inner, value.map(PyObjectRef::from)).map(|obj| {
// SAFETY: a typed cell only stores references of payload `T`.
unsafe { obj.downcast_unchecked() }
})
}
/// Store `value` only when the cell is empty.
///
/// The value placed in the slot is not marked published, so it can still
/// return to the freelist. With threading, the value that leaves the slot
/// is marked so its free waits out a reader that already loaded it.
pub(crate) fn store(&self, value: Option<PyObjectRef>) -> Option<PyObjectRef> {
let new_ptr = match value {
Some(obj) => {
let ptr = obj.into_raw().as_ptr();
ptr.expose_provenance();
ptr.cast()
/// On failure the cell is unchanged and `value` is returned still owned.
pub(crate) fn compare_exchange_empty(&self, value: PyRef<T>) -> Result<(), PyRef<T>> {
// SAFETY: every `PyAtomicRef<_>` is an atomic pointer plus a
// zero-sized marker, so the layouts match. The untyped cell API only
// loads and stores that pointer.
let cell = unsafe { &*core::ptr::from_ref(self).cast::<PyAtomicRef<Option<PyObject>>>() };
cell.compare_exchange_empty(value.into()).map_err(|obj| {
// SAFETY: a typed cell only stores references of payload `T`.
unsafe { obj.downcast_unchecked() }
})
}
}
fn empty_cell<T>() -> PyAtomicRef<T> {
PyAtomicRef {
inner: Radium::new(null_mut()),
_phantom: PhantomData,
}
}
fn cell_load_ptr(inner: &PyAtomic<*mut u8>) -> *mut PyObject {
inner.load(Ordering::Acquire).cast()
}
/// Try-incref the pointer in `inner`.
///
/// A concurrent store may drop the previous value. The incref is retried
/// until it applies to the pointer still in the slot. Returns `None` when
/// the slot is empty.
fn cell_load_owned(inner: &PyAtomic<*mut u8>) -> Option<PyObjectRef> {
let ptr = inner.load(Ordering::Acquire);
if ptr.is_null() {
return None;
}
// Without threading the slot's own reference keeps the object alive,
// so one incref is enough. With threading, retry when a store retires
// the pointer between the load and the incref.
#[cfg(not(feature = "threading"))]
{
// SAFETY: `ptr` is non-null and the cell's own reference keeps the
// object alive for this incref.
unsafe { PyObject::try_to_owned_from_ptr(ptr.cast()) }
}
#[cfg(feature = "threading")]
{
let mut ptr = ptr;
loop {
// SAFETY: `ptr` is non-null. A value that left the cell was marked
// published, so its refcount word stays readable until QSBR.
if let Some(obj) = unsafe { PyObject::try_to_owned_from_ptr(ptr.cast()) }
&& core::ptr::eq(inner.load(Ordering::Acquire), ptr)
{
return Some(obj);
}
None => null_mut(),
};
let old = Radium::swap(&self.inner, new_ptr, Ordering::AcqRel);
let old = NonNull::new(old.cast()).map(|ptr| unsafe { PyObjectRef::from_raw(ptr) });
#[cfg(feature = "threading")]
if let Some(old) = old.as_ref() {
old.mark_cache_published();
ptr = inner.load(Ordering::Acquire);
if ptr.is_null() {
return None;
}
core::hint::spin_loop();
}
}
}
/// Replace the stored reference. Returns the previous one, still owned.
///
/// The value placed in the slot is not marked published, so it can still
/// return to the freelist. With threading, the value that leaves the slot
/// is marked so its free waits out a reader that already loaded it.
fn cell_store(inner: &PyAtomic<*mut u8>, value: Option<PyObjectRef>) -> Option<PyObjectRef> {
let new_ptr = match value {
Some(obj) => {
let ptr = obj.into_raw().as_ptr();
ptr.expose_provenance();
ptr.cast()
}
None => null_mut(),
};
let old = Radium::swap(inner, new_ptr, Ordering::AcqRel);
// SAFETY: a non-null slot pointer is an owning reference the cell just released.
let old = NonNull::new(old.cast()).map(|ptr| unsafe { PyObjectRef::from_raw(ptr) });
#[cfg(feature = "threading")]
if let Some(old) = old.as_ref() {
old.mark_cache_published();
}
old
}
/// Store `value` only when the cell is empty.
///
/// On failure the cell is unchanged and `value` is returned still owned.
fn cell_compare_exchange_empty(
inner: &PyAtomic<*mut u8>,
value: PyObjectRef,
) -> Result<(), PyObjectRef> {
let raw = value.into_raw();
let ptr = raw.as_ptr();
ptr.expose_provenance();
match inner.compare_exchange(
core::ptr::null_mut(),
ptr.cast(),
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => Ok(()),
Err(_) => {
// SAFETY: the exchange did not take the pointer, so `raw` is
// still the unique owning reference.
Err(unsafe { PyObjectRef::from_raw(raw) })
}
old
}
}
@@ -547,6 +584,21 @@ impl Deref for PyAtomicRef<PyObject> {
}
impl PyAtomicRef<PyObject> {
/// Strong reference to the current value.
///
/// The cell is never null. A concurrent store may drop the previous value;
/// the incref is retried until it applies to the pointer still in the slot.
pub(crate) fn load_owned(&self) -> PyObjectRef {
cell_load_owned(&self.inner).expect("non-null atomic cell")
}
/// Replace the stored reference. Returns the previous one, still owned.
///
/// The cell is never null, before and after the store.
pub(crate) fn store(&self, value: PyObjectRef) -> PyObjectRef {
cell_store(&self.inner, Some(value)).expect("non-null atomic cell")
}
/// # Safety
/// The caller is responsible to keep the returned PyRef alive
/// until no more reference can be used via PyAtomicRef::deref()
@@ -576,6 +628,42 @@ impl From<Option<PyObjectRef>> for PyAtomicRef<Option<PyObject>> {
}
impl PyAtomicRef<Option<PyObject>> {
/// Empty slot. The pointer is null and owns no reference.
pub(crate) fn new_empty() -> Self {
empty_cell()
}
/// Borrowed pointer currently stored. Null when the slot is empty.
///
/// The slot owns the reference, so this stays valid while the slot is
/// unchanged. Traversal calls it with other threads stopped.
pub(crate) fn load_ptr(&self) -> *mut PyObject {
cell_load_ptr(&self.inner)
}
/// Strong reference to the current value, or `None` when the slot is empty.
///
/// This is the owned read for a nullable cell. A concurrent store may drop
/// the previous value; the incref is retried until it applies to the
/// pointer still in the slot. Published-object memory is reclaimed only
/// after a QSBR grace period (see `object::qsbr`), so the refcount word of
/// a swapped-out value stays readable.
pub fn load_owned(&self) -> Option<PyObjectRef> {
cell_load_owned(&self.inner)
}
/// Replace the stored reference. Returns the previous one, still owned.
pub(crate) fn store(&self, value: Option<PyObjectRef>) -> Option<PyObjectRef> {
cell_store(&self.inner, value)
}
/// Store `value` only when the cell is empty.
///
/// On failure the cell is unchanged and `value` is returned still owned.
pub(crate) fn compare_exchange_empty(&self, value: PyObjectRef) -> Result<(), PyObjectRef> {
cell_compare_exchange_empty(&self.inner, value)
}
pub fn deref(&self) -> Option<&PyObject> {
self.deref_ordering(Ordering::Relaxed)
}
@@ -584,14 +672,6 @@ impl PyAtomicRef<Option<PyObject>> {
unsafe { self.inner.load(ordering).cast::<PyObject>().as_ref() }
}
pub fn to_owned(&self) -> Option<PyObjectRef> {
self.to_owned_ordering(Ordering::Relaxed)
}
pub fn to_owned_ordering(&self, ordering: Ordering) -> Option<PyObjectRef> {
self.deref_ordering(ordering).map(|x| x.to_owned())
}
/// # Safety
/// The caller is responsible to keep the returned PyRef alive
/// until no more reference can be used via PyAtomicRef::deref()
+18
View File
@@ -116,10 +116,28 @@ where
}
}
unsafe impl<T: crate::PyPayload> Traverse for super::ext::PyAtomicRef<Option<T>> {
#[inline]
fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
if let Some(obj) = self.deref() {
traverse_fn(obj.as_object());
}
}
}
unsafe impl Traverse for super::ext::PyAtomicRef<PyObject> {
#[inline]
fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
traverse_fn(self);
}
}
unsafe impl Traverse for super::ext::PyAtomicRef<Option<PyObject>> {
#[inline]
fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
let ptr = self.load_ptr();
// SAFETY: traversal runs with other threads stopped, so a non-null
// slot pointer stays allocated for this borrow.
if let Some(obj) = unsafe { ptr.as_ref() } {
traverse_fn(obj);
}
+1 -1
View File
@@ -48,7 +48,7 @@ impl PyObjVTable {
unsafe impl Traverse for InstanceDict {
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
self.d.traverse(tracer_fn)
self.dict.traverse(tracer_fn)
}
}
+1 -1
View File
@@ -395,7 +395,7 @@ impl SaturatedSlice {
let start = to_isize_index(vm, slice.start_ref(vm))?
.unwrap_or_else(|| if step.is_negative() { isize::MAX } else { 0 });
let stop = to_isize_index(vm, &slice.stop(vm))?.unwrap_or_else(|| {
let stop = to_isize_index(vm, &slice.stop)?.unwrap_or_else(|| {
if step.is_negative() {
isize::MIN
} else {
-8
View File
@@ -134,7 +134,6 @@ mod _abc {
Ok(())
}
/// Returns the current ABC cache token.
#[pyfunction]
fn get_cache_token() -> u64 {
get_invalidation_counter()
@@ -203,7 +202,6 @@ mod _abc {
cls: PyObjectRef,
}
/// Internal ABC helper for class set-up. Should be never used outside abc module.
#[pyfunction]
fn _abc_init(AbcSelf { cls }: AbcSelf, vm: &VirtualMachine) -> PyResult<()> {
compute_abstract_methods(&cls, vm)?;
@@ -220,7 +218,6 @@ mod _abc {
Ok(())
}
/// Internal ABC helper for subclass registration. Should be never used outside abc module.
#[pyfunction]
fn _abc_register(
AbcSelf { cls }: AbcSelf,
@@ -269,7 +266,6 @@ mod _abc {
Ok(subclass)
}
/// Internal ABC helper for instance checks. Should be never used outside abc module.
#[pyfunction]
fn _abc_instancecheck(
AbcSelf { cls }: AbcSelf,
@@ -345,7 +341,6 @@ mod _abc {
Ok(None)
}
/// Internal ABC helper for subclass checks. Should be never used outside abc module.
#[pyfunction]
fn _abc_subclasscheck(
AbcSelf { cls }: AbcSelf,
@@ -431,7 +426,6 @@ mod _abc {
Ok(false)
}
/// Internal ABC helper for cache and registry debugging.
#[pyfunction]
fn _get_dump(AbcSelf { cls }: AbcSelf, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
let impl_data = get_impl(&cls, vm)?;
@@ -473,7 +467,6 @@ mod _abc {
]))
}
/// Internal ABC helper to reset registry of a given class.
#[pyfunction]
fn _reset_registry(AbcSelf { cls }: AbcSelf, vm: &VirtualMachine) -> PyResult<()> {
let impl_data = get_impl(&cls, vm)?;
@@ -485,7 +478,6 @@ mod _abc {
Ok(())
}
/// Internal ABC helper to reset both caches of a given class.
#[pyfunction]
fn _reset_caches(AbcSelf { cls }: AbcSelf, vm: &VirtualMachine) -> PyResult<()> {
let impl_data = get_impl(&cls, vm)?;
+40 -60
View File
@@ -19,7 +19,7 @@ mod _collections {
function::{
ArgIterable, FuncArgs, KwArgs, OptionalArg, PyComparisonValue, PySetterValue, PySsize,
},
object::{Traverse, TraverseFn},
object::{PyAtomicRef, Traverse, TraverseFn},
protocol::{PyIter, PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods},
recursion::ReprGuard,
sequence::{MutObjectSequenceOp, OptionalRangeArgs},
@@ -925,10 +925,20 @@ mod _collections {
unhashable = true,
traverse = "manual"
)]
#[derive(Debug, Default)]
#[derive(Debug)]
struct PyDefaultDict {
dict: PyDict,
default_factory: PyRwLock<Option<PyObjectRef>>,
#[pymember(writable)]
default_factory: PyAtomicRef<Option<PyObject>>,
}
impl Default for PyDefaultDict {
fn default() -> Self {
Self {
dict: PyDict::default(),
default_factory: PyAtomicRef::new_empty(),
}
}
}
// SAFETY: Traverse visits each owned Python reference at most once.
@@ -940,7 +950,7 @@ mod _collections {
fn clear(&mut self, out: &mut Vec<PyObjectRef>) {
Traverse::clear(&mut self.dict, out);
if let Some(factory) = self.default_factory.get_mut().take() {
if let Some(factory) = self.default_factory.store(None) {
out.push(factory);
}
}
@@ -951,33 +961,12 @@ mod _collections {
flags(BASETYPE, MAPPING, HAS_DICT)
)]
impl PyDefaultDict {
#[pymember(type = "object")]
fn default_factory(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(zelf
.default_factory
.read()
.clone()
.unwrap_or_else(|| vm.ctx.none()))
}
#[pymember(type = "object", setter)]
fn set_default_factory(
vm: &VirtualMachine,
zelf: PyObjectRef,
value: PySetterValue,
) -> PyResult<()> {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
*zelf.default_factory.write() = match value {
PySetterValue::Assign(v) if !v.is(&vm.ctx.none()) => Some(v),
_ => None,
};
Ok(())
}
#[pymethod]
fn __missing__(&self, object: PyObjectRef, vm: &VirtualMachine) -> PyResult {
let factory = self.default_factory.read().clone();
let factory = self
.default_factory
.load_owned()
.filter(|factory| !vm.is_none(factory));
if let Some(f) = factory {
let value = f.call((), vm)?;
@@ -990,11 +979,14 @@ mod _collections {
#[pymethod]
#[pymethod(name = "__copy__")]
fn copy(&self) -> Self {
let default_factory = self.default_factory.read().clone();
let default_factory = match self.default_factory.load_owned() {
Some(factory) => PyAtomicRef::from(Some(factory)),
None => PyAtomicRef::new_empty(),
};
Self {
dict: self.dict.copy(),
default_factory: PyRwLock::new(default_factory),
default_factory,
}
}
@@ -1002,7 +994,11 @@ mod _collections {
fn __reduce__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
let cls = zelf.class().to_owned();
let default_factory = zelf.default_factory.read().clone();
// NULL and None both mean "no factory": the args tuple is empty.
let default_factory = zelf
.default_factory
.load_owned()
.filter(|factory| !vm.is_none(factory));
let factory_tuple_elements =
default_factory.map_or_else(Vec::new, |factory| vec![factory]);
let factory_tuple = vm.ctx.new_tuple(factory_tuple_elements);
@@ -1034,13 +1030,13 @@ mod _collections {
return not_implemented();
}
(zelf.default_factory.read().clone(), zelf.dict.copy())
(zelf.default_factory.load_owned(), zelf.dict.copy())
} else if let Some(zelf) = rhs.downcast_ref::<Self>() {
let Some(dict) = lhs.downcast_ref::<PyDict>() else {
return not_implemented();
};
(zelf.default_factory.read().clone(), dict.copy())
(zelf.default_factory.load_owned(), dict.copy())
} else {
return Err(vm.new_type_error(format!(
"unsupported operand type(s) for |: '{}' and '{}'",
@@ -1053,7 +1049,10 @@ mod _collections {
Ok(Self {
dict,
default_factory: PyRwLock::new(default_factory),
default_factory: match default_factory {
Some(factory) => PyAtomicRef::from(Some(factory)),
None => PyAtomicRef::new_empty(),
},
}
.to_pyobject(vm))
}
@@ -1077,7 +1076,7 @@ mod _collections {
}
})?;
*zelf.default_factory.write() = default_factory;
zelf.default_factory.store(default_factory);
zelf.dict.update(
OptionalArg::from_option(args.take_positional()),
@@ -1091,7 +1090,7 @@ mod _collections {
impl Representable for PyDefaultDict {
fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
let default_factory = zelf.default_factory.read();
let default_factory = zelf.default_factory.load_owned();
let factory_repr = match default_factory.as_ref() {
Some(factory) => {
@@ -1183,7 +1182,8 @@ mod _collections {
struct PyTupleGetter {
#[pytraverse(skip)]
index: isize,
doc: PyRwLock<Option<PyObjectRef>>,
#[pymember(name = "__doc__", writable)]
doc: PyAtomicRef<Option<PyObject>>,
}
impl Constructor for PyTupleGetter {
@@ -1196,7 +1196,7 @@ mod _collections {
) -> PyResult<Self> {
Ok(Self {
index,
doc: PyRwLock::new(Some(doc)),
doc: PyAtomicRef::from(Some(doc)),
})
}
}
@@ -1204,27 +1204,7 @@ mod _collections {
#[pyclass(with(Constructor, GetDescriptor, Representable))]
impl PyTupleGetter {
fn doc(&self, vm: &VirtualMachine) -> PyObjectRef {
self.doc.read().clone().unwrap_or_else(|| vm.ctx.none())
}
#[pymember(type = "object")]
fn __doc__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(zelf.doc(vm))
}
#[pymember(type = "object", setter)]
fn set___doc__(
vm: &VirtualMachine,
zelf: PyObjectRef,
value: PySetterValue,
) -> PyResult<()> {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
*zelf.doc.write() = match value {
PySetterValue::Assign(doc) => Some(doc),
PySetterValue::Delete => None,
};
Ok(())
self.doc.load_owned().unwrap_or_else(|| vm.ctx.none())
}
#[pyslot]
-6
View File
@@ -335,7 +335,6 @@ pub(crate) mod _ctypes {
exc_type
}
/// Get the size of a ctypes type or instance
#[pyfunction]
pub(crate) fn sizeof(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> {
use super::structure::PyCStructType;
@@ -847,11 +846,6 @@ pub(crate) mod _ctypes {
super::function::cast_impl(&obj, obj.clone(), &typ, vm)
}
/// Return buffer interface information for a ctypes type or object.
/// Returns a tuple (format, ndim, shape) where:
/// - format: PEP 3118 format string
/// - ndim: number of dimensions
/// - shape: tuple of dimension sizes
#[pyfunction]
fn buffer_info(object: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// Determine if object is a type or an instance
+1 -2
View File
@@ -388,8 +388,7 @@ impl AsNumber for PyCArrayType {
}
}
/// PyCArray - Array instance
/// All array metadata (element_type, length, element_size) is stored in the type's StgInfo
// All array metadata (element_type, length, element_size) is stored in the type's StgInfo
#[pyclass(
name = "Array",
base = PyCData,
-1
View File
@@ -1245,7 +1245,6 @@ impl PyCData {
// PyCField - Field descriptor for Structure/Union types
/// CField descriptor for Structure/Union field access
#[pyclass(name = "CField", module = "_ctypes")]
#[derive(Debug, PyPayload)]
pub struct PyCField {
+1 -2
View File
@@ -344,8 +344,7 @@ impl PyCFuncPtrType {
}
}
/// PyCFuncPtr - Function pointer instance
/// Saved in _base.buffer
// Saved in _base.buffer
#[pyclass(
module = "_ctypes",
name = "CFuncPtr",
+1 -2
View File
@@ -249,8 +249,7 @@ impl AsNumber for PyCPointerType {
}
}
/// PyCPointer - Pointer instance
/// `contents` is a computed property, not a stored field.
// `contents` is a computed property, not a stored field.
#[pyclass(
name = "_Pointer",
base = PyCData,
@@ -662,7 +662,6 @@ impl SetAttr for PyCStructType {
}
}
/// PyCStructure - base class for Structure instances
#[pyclass(
module = "_ctypes",
name = "Structure",
-1
View File
@@ -534,7 +534,6 @@ impl SetAttr for PyCUnionType {
}
}
/// PyCUnion - base class for Union
#[pyclass(module = "_ctypes", name = "Union", base = PyCData, metaclass = "PyCUnionType")]
#[repr(transparent)]
pub(crate) struct PyCUnion(pub PyCData);
+3 -3
View File
@@ -692,9 +692,9 @@ mod _functools {
#[pyclass(flags(IMMUTABLETYPE, DISALLOW_INSTANTIATION))]
impl PyLruListElem {}
/// Native implementation of `functools._lru_cache_wrapper`, mirroring CPython's
/// `_functools` accelerator so `functools.lru_cache` doesn't fall back to the much
/// slower pure-Python implementation in `Lib/functools.py`.
// Native implementation of `functools._lru_cache_wrapper`, mirroring CPython's
// `_functools` accelerator so `functools.lru_cache` doesn't fall back to the much
// slower pure-Python implementation in `Lib/functools.py`.
#[pyattr]
#[pyclass(name = "_lru_cache_wrapper", module = "functools")]
#[derive(PyPayload)]
-21
View File
@@ -350,14 +350,6 @@ mod _operator {
Ok(res)
}
/// attrgetter(attr, /, *attrs)
/// --
///
/// Return a callable object that fetches the given attribute(s) from its operand.
/// After f = attrgetter('name'), the call f(r) returns r.name.
/// After g = attrgetter('name', 'date'), the call g(r) returns (r.name, r.date).
/// After h = attrgetter('name.first', 'name.last'), the call h(r) returns
/// (r.name.first, r.name.last).
#[pyattr]
#[pyclass(name = "attrgetter")]
#[derive(Debug, PyPayload)]
@@ -475,12 +467,6 @@ mod _operator {
}
}
/// itemgetter(item, /, *items)
/// --
///
/// Return a callable object that fetches the given item(s) from its operand.
/// After f = itemgetter(2), the call f(r) returns r[2].
/// After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3])
#[pyattr]
#[pyclass(name = "itemgetter")]
#[derive(Debug, PyPayload)]
@@ -567,13 +553,6 @@ mod _operator {
}
}
/// methodcaller(name, /, *args, **kwargs)
/// --
///
/// Return a callable object that calls the given method on its operand.
/// After f = methodcaller('name'), the call f(r) returns r.name().
/// After g = methodcaller('name', 'date', foo=1), the call g(r) returns
/// r.name('date', foo=1).
#[pyattr]
#[pyclass(name = "methodcaller")]
#[derive(Debug, PyPayload)]
+2 -4
View File
@@ -373,7 +373,7 @@ pub(crate) mod _thread {
}
}
/// Get thread identity - uses pthread_self() on Unix for fork compatibility
// uses pthread_self() on Unix for fork compatibility
#[pyfunction]
#[must_use]
pub fn get_ident() -> u64 {
@@ -999,8 +999,7 @@ pub(crate) mod _thread {
vm.state.main_thread_ident.store(ident);
}
/// ExceptHookArgs - simple class to hold exception hook arguments
/// This allows threading.py to import _excepthook and _ExceptHookArgs from _thread
// This allows threading.py to import _excepthook and _ExceptHookArgs from _thread
#[pyattr]
#[pyclass(module = "_thread", name = "_ExceptHookArgs")]
#[derive(Debug, PyPayload)]
@@ -1065,7 +1064,6 @@ pub(crate) mod _thread {
}
}
/// Handle uncaught exception in Thread.run()
#[pyfunction]
fn _excepthook(args: crate::PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
// Type check: args must be _ExceptHookArgs
+1 -5
View File
@@ -195,6 +195,7 @@ pub(crate) mod decl {
#[pyclass(name, module = "typing")]
#[derive(Debug, PyPayload)]
pub(crate) struct TypeAliasType {
#[pymember(name = "__name__")]
name: PyStrRef,
type_params: PyTupleRef,
compute_value: PyObjectRef,
@@ -240,11 +241,6 @@ pub(crate) mod decl {
}
}
#[pygetset]
fn __name__(&self) -> PyObjectRef {
self.name.clone().into()
}
#[pygetset]
fn __value__(&self, vm: &VirtualMachine) -> PyResult {
let cached = self.cached_value.lock().clone();
-5
View File
@@ -674,7 +674,6 @@ mod _winapi {
vm.ctx.new_str(result_str)
}
/// GetShortPathName - Return the short version of the provided path.
#[pyfunction]
fn GetShortPathName(path: PyStrRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
let path = path
@@ -685,7 +684,6 @@ mod _winapi {
Ok(path_name_result_to_pystr(wide, vm))
}
/// GetLongPathName - Return the long version of the provided path.
#[pyfunction]
fn GetLongPathName(path: PyStrRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
let path = path
@@ -915,7 +913,6 @@ mod _winapi {
const MAXIMUM_WAIT_OBJECTS: usize = host_winapi::MAXIMUM_WAIT_OBJECTS as usize;
/// BatchedWaitForMultipleObjects - Wait for multiple handles, supporting more than 64.
#[pyfunction]
fn BatchedWaitForMultipleObjects(
handle_seq: PyObjectRef,
@@ -1070,7 +1067,6 @@ mod _winapi {
host_winapi::virtual_query_size(address).map_err(|e| e.to_pyexception(vm))
}
/// CopyFile2 - Copy a file with extended parameters.
#[pyfunction]
fn CopyFile2(
existing_file_name: PyStrRef,
@@ -1090,7 +1086,6 @@ mod _winapi {
host_winapi::copy_file2(&src_wide, &dst_wide, flags).map_err(|e| e.to_pyexception(vm))
}
/// _mimetypes_read_windows_registry - Read MIME type associations from registry.
#[pyfunction]
fn _mimetypes_read_windows_registry(
on_type_read: PyObjectRef,
-16
View File
@@ -21,19 +21,16 @@ mod gc {
#[pyattr]
const DEBUG_LEAK: u32 = gc_state::GcDebugFlags::LEAK.bits();
/// Enable automatic garbage collection.
#[pyfunction]
fn enable(vm: &VirtualMachine) {
vm.state.gc.enable();
}
/// Disable automatic garbage collection.
#[pyfunction]
fn disable(vm: &VirtualMachine) {
vm.state.gc.disable();
}
/// Return true if automatic gc is enabled.
#[pyfunction]
fn isenabled(vm: &VirtualMachine) -> bool {
vm.state.gc.is_enabled()
@@ -77,7 +74,6 @@ mod gc {
Ok((result.collected + result.uncollectable) as i32)
}
/// Return the current collection thresholds as a tuple.
#[pyfunction]
fn get_threshold(vm: &VirtualMachine) -> PyObjectRef {
let (t0, t1, t2) = vm.state.gc.get_threshold();
@@ -90,7 +86,6 @@ mod gc {
.into()
}
/// Set the collection thresholds.
#[pyfunction]
fn set_threshold(
threshold0: u32,
@@ -105,7 +100,6 @@ mod gc {
);
}
/// Return the current collection counts as a tuple.
#[pyfunction]
fn get_count(vm: &VirtualMachine) -> PyObjectRef {
let (c0, c1, c2) = gc_state::gc_state().get_count();
@@ -118,13 +112,11 @@ mod gc {
.into()
}
/// Return the current debugging flags.
#[pyfunction]
fn get_debug(vm: &VirtualMachine) -> u32 {
vm.state.gc.get_debug().bits()
}
/// Set the debugging flags.
#[pyfunction]
fn set_debug(flags: u32, vm: &VirtualMachine) {
vm.state
@@ -132,7 +124,6 @@ mod gc {
.set_debug(gc_state::GcDebugFlags::from_bits_truncate(flags));
}
/// Return a list of per-generation gc stats.
#[pyfunction]
fn get_stats(vm: &VirtualMachine) -> PyResult<PyListRef> {
let stats = vm.state.gc.get_stats();
@@ -174,7 +165,6 @@ mod gc {
Ok(vm.ctx.new_list(objects))
}
/// Return the list of objects directly referred to by any of the arguments.
#[pyfunction]
fn get_referents(objs: PosArgs<PyObjectRef, NameObjs>, vm: &VirtualMachine) -> PyListRef {
let mut result = Vec::new();
@@ -187,7 +177,6 @@ mod gc {
vm.ctx.new_list(result)
}
/// Return the list of objects that directly refer to any of the arguments.
#[pyfunction]
fn get_referrers(objs: PosArgs<PyObjectRef, NameObjs>, vm: &VirtualMachine) -> PyListRef {
use std::collections::HashSet;
@@ -243,32 +232,27 @@ mod gc {
vm.ctx.new_list(result)
}
/// Return True if the object is tracked by the garbage collector.
#[pyfunction]
fn is_tracked(obj: PyObjectRef) -> bool {
// An object is tracked if it has IS_TRACE = true (has a trace function)
obj.is_gc_tracked()
}
/// Return True if the object has been finalized by the garbage collector.
#[pyfunction]
fn is_finalized(obj: PyObjectRef) -> bool {
obj.gc_finalized()
}
/// Freeze all objects tracked by gc.
#[pyfunction]
fn freeze(vm: &VirtualMachine) {
vm.state.gc.freeze();
}
/// Unfreeze all objects in the permanent generation.
#[pyfunction]
fn unfreeze(vm: &VirtualMachine) {
vm.state.gc.unfreeze();
}
/// Return the number of objects in the permanent generation.
#[pyfunction]
fn get_freeze_count() -> usize {
gc_state::gc_state().get_freeze_count()
+8 -17
View File
@@ -203,8 +203,7 @@ pub(crate) mod module {
}
}
/// Get the real file name (with correct case) without accessing the file.
/// Uses FindFirstFileW to get the name as stored on the filesystem.
// Uses FindFirstFileW to get the name as stored on the filesystem.
#[pyfunction]
fn _findfirstfile(path: OsPath, vm: &VirtualMachine) -> PyResult<PyStrRef> {
let wide = path.to_wide_cstring(vm)?;
@@ -305,55 +304,48 @@ pub(crate) mod module {
}
}
/// Check if a path is a directory.
/// return _testFileType(path, PY_IFDIR)
// return _testFileType(path, PY_IFDIR)
#[pyfunction]
fn _path_isdir(args: PathArg, vm: &VirtualMachine) -> bool {
args.to_path_or_fd(vm)
.is_some_and(|p| _test_file_type(&p, PY_IFDIR))
}
/// Check if a path is a regular file.
/// return _testFileType(path, PY_IFREG)
// return _testFileType(path, PY_IFREG)
#[pyfunction]
fn _path_isfile(args: PathArg, vm: &VirtualMachine) -> bool {
args.to_path_or_fd(vm)
.is_some_and(|p| _test_file_type(&p, PY_IFREG))
}
/// Check if a path is a symbolic link.
/// return _testFileType(path, PY_IFLNK)
// return _testFileType(path, PY_IFLNK)
#[pyfunction]
fn _path_islink(args: PathArg, vm: &VirtualMachine) -> bool {
args.to_path_or_fd(vm)
.is_some_and(|p| _test_file_type(&p, PY_IFLNK))
}
/// Check if a path is a junction (mount point).
/// return _testFileType(path, PY_IFMNT)
// return _testFileType(path, PY_IFMNT)
#[pyfunction]
fn _path_isjunction(args: PathArg, vm: &VirtualMachine) -> bool {
args.to_path_or_fd(vm)
.is_some_and(|p| _test_file_type(&p, PY_IFMNT))
}
/// Check if a path exists (follows symlinks).
/// return _testFileExists(path, TRUE)
// return _testFileExists(path, TRUE)
#[pyfunction]
fn _path_exists(args: PathArg, vm: &VirtualMachine) -> bool {
args.to_path_or_fd(vm)
.is_some_and(|p| _test_file_exists(&p, true))
}
/// Check if a path exists (does not follow symlinks).
/// return _testFileExists(path, FALSE)
// return _testFileExists(path, FALSE)
#[pyfunction]
fn _path_lexists(args: PathArg, vm: &VirtualMachine) -> bool {
args.to_path_or_fd(vm)
.is_some_and(|p| _test_file_exists(&p, false))
}
/// Check if a path is on a Windows Dev Drive.
#[pyfunction]
fn _path_isdevdrive(path: OsPath, vm: &VirtualMachine) -> PyResult<bool> {
let path = path.to_wide_cstring(vm)?;
@@ -1037,8 +1029,7 @@ pub(crate) mod module {
host_nt::dup2(args.fd, args.fd2, args.inheritable).map_err(|e| e.to_pyexception(vm))
}
/// Windows-specific readlink that preserves \\?\ prefix for junctions
/// returns the substitute name from reparse data which includes the prefix
// returns the substitute name from reparse data which includes the prefix
#[pyfunction]
fn readlink(path: OsPath, vm: &VirtualMachine) -> PyResult {
let mode = path.mode();
+2 -7
View File
@@ -764,7 +764,6 @@ pub mod sys {
false // RustPython has no GIL (like free-threaded Python)
}
/// Return True if remote debugging is enabled, False otherwise.
#[pyfunction]
const fn is_remote_debug_enabled() -> bool {
false // RustPython does not support remote debugging
@@ -1063,8 +1062,6 @@ pub mod sys {
})
}
/// Return a dictionary mapping each thread's identifier to the topmost stack frame
/// currently active in that thread at the time the function is called.
#[cfg(feature = "threading")]
#[pyfunction]
fn _current_frames(vm: &VirtualMachine) -> PyResult<PyDictRef> {
@@ -1082,8 +1079,6 @@ pub mod sys {
Ok(dict)
}
/// Return a dictionary mapping each thread's identifier to its currently
/// active exception, or None if no exception is active.
#[cfg(feature = "threading")]
#[pyfunction]
fn _current_exceptions(vm: &VirtualMachine) -> PyResult<PyDictRef> {
@@ -1109,7 +1104,7 @@ pub mod sys {
Ok(dict)
}
/// Stub for non-threading builds - returns empty dict
// Stub for non-threading builds - returns empty dict
#[cfg(not(feature = "threading"))]
#[pyfunction]
fn _current_frames(vm: &VirtualMachine) -> PyDictRef {
@@ -1261,7 +1256,7 @@ pub mod sys {
PyIntInfo::from_data(IntInfoData::INFO, vm)
}
/// Private function for getting PyConfig.cpu_count
// Private function for getting PyConfig.cpu_count
#[pyfunction]
fn _get_cpu_count_config(vm: &VirtualMachine) -> i32 {
vm.state.config.settings.cpu_count.map_or(-1, |n| n.get())
+1 -1
View File
@@ -467,7 +467,7 @@ mod decl {
}
}
/// https://docs.python.org/3/library/time.html?highlight=gmtime#time.gmtime
// https://docs.python.org/3/library/time.html?highlight=gmtime#time.gmtime
#[pyfunction]
fn gmtime(
secs: OptionalOption<Either<f64, i64>>,
+15 -66
View File
@@ -79,6 +79,7 @@ pub(crate) mod typevar {
#[pyclass(name = "TypeVar", module = "typing")]
#[derive(Debug, PyPayload)]
pub struct TypeVar {
#[pymember(name = "__name__")]
name: PyObjectRef, // TODO PyStrRef?
bound: PyMutex<PyObjectRef>,
evaluate_bound: PyObjectRef,
@@ -86,8 +87,11 @@ pub(crate) mod typevar {
evaluate_constraints: PyObjectRef,
default_value: PyMutex<PyObjectRef>,
evaluate_default: PyMutex<PyObjectRef>,
#[pymember(name = "__covariant__")]
covariant: bool,
#[pymember(name = "__contravariant__")]
contravariant: bool,
#[pymember(name = "__infer_variance__")]
infer_variance: bool,
}
@@ -101,11 +105,6 @@ pub(crate) mod typevar {
Err(vm.new_type_error("Cannot subclass an instance of TypeVar"))
}
#[pygetset]
fn __name__(&self) -> PyObjectRef {
self.name.clone()
}
#[pygetset]
fn __constraints__(&self, vm: &VirtualMachine) -> PyResult {
let mut constraints = self.constraints.lock();
@@ -136,21 +135,6 @@ pub(crate) mod typevar {
Ok(r)
}
#[pygetset]
const fn __covariant__(&self) -> bool {
self.covariant
}
#[pygetset]
const fn __contravariant__(&self) -> bool {
self.contravariant
}
#[pygetset]
const fn __infer_variance__(&self) -> bool {
self.infer_variance
}
#[pygetset]
fn __default__(&self, vm: &VirtualMachine) -> PyResult {
{
@@ -455,12 +439,17 @@ pub(crate) mod typevar {
#[pyclass(name = "ParamSpec", module = "typing")]
#[derive(Debug, PyPayload)]
pub struct ParamSpec {
#[pymember(name = "__name__")]
name: PyObjectRef,
#[pymember(name = "__bound__")]
bound: Option<PyObjectRef>,
default_value: PyMutex<PyObjectRef>,
evaluate_default: PyMutex<PyObjectRef>,
#[pymember(name = "__covariant__")]
covariant: bool,
#[pymember(name = "__contravariant__")]
contravariant: bool,
#[pymember(name = "__infer_variance__")]
infer_variance: bool,
}
@@ -474,11 +463,6 @@ pub(crate) mod typevar {
Err(vm.new_type_error("Cannot subclass an instance of ParamSpec"))
}
#[pygetset]
fn __name__(&self) -> PyObjectRef {
self.name.clone()
}
#[pygetset]
fn args(zelf: crate::PyRef<Self>, vm: &VirtualMachine) -> PyObjectRef {
let self_obj: PyObjectRef = zelf.into();
@@ -497,29 +481,6 @@ pub(crate) mod typevar {
psk.into_ref(&vm.ctx).into()
}
#[pygetset]
fn __bound__(&self, vm: &VirtualMachine) -> PyObjectRef {
if let Some(bound) = self.bound.clone() {
return bound;
}
vm.ctx.none()
}
#[pygetset]
const fn __covariant__(&self) -> bool {
self.covariant
}
#[pygetset]
const fn __contravariant__(&self) -> bool {
self.contravariant
}
#[pygetset]
const fn __infer_variance__(&self) -> bool {
self.infer_variance
}
#[pygetset]
fn __default__(&self, vm: &VirtualMachine) -> PyResult {
{
@@ -687,7 +648,7 @@ pub(crate) mod typevar {
impl Representable for ParamSpec {
#[inline(always)]
fn repr_str(zelf: &crate::Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
let name = zelf.__name__().str_utf8(vm)?;
let name = zelf.name.str_utf8(vm)?;
Ok(variance_repr(
name.as_str(),
zelf.infer_variance,
@@ -715,6 +676,7 @@ pub(crate) mod typevar {
#[pyclass(name = "TypeVarTuple", module = "typing")]
#[derive(Debug, PyPayload)]
pub struct TypeVarTuple {
#[pymember(name = "__name__")]
name: PyObjectRef,
default_value: PyMutex<PyObjectRef>,
evaluate_default: PyMutex<PyObjectRef>,
@@ -725,11 +687,6 @@ pub(crate) mod typevar {
with(Constructor, Representable, Iterable)
)]
impl TypeVarTuple {
#[pygetset]
fn __name__(&self) -> PyObjectRef {
self.name.clone()
}
#[pygetset]
fn __default__(&self, vm: &VirtualMachine) -> PyResult {
{
@@ -891,6 +848,7 @@ pub(crate) mod typevar {
#[pyclass(name = "ParamSpecArgs", module = "typing")]
#[derive(Debug, PyPayload)]
pub struct ParamSpecArgs {
#[pymember]
__origin__: PyObjectRef,
}
@@ -900,11 +858,6 @@ pub(crate) mod typevar {
fn __mro_entries__(&self, _object: PyObjectRef, vm: &VirtualMachine) -> PyResult {
Err(vm.new_type_error("Cannot subclass an instance of ParamSpecArgs"))
}
#[pygetset]
fn __origin__(&self) -> PyObjectRef {
self.__origin__.clone()
}
}
impl Constructor for ParamSpecArgs {
@@ -922,7 +875,7 @@ pub(crate) mod typevar {
// A ParamSpec origin is named; anything else is shown by its repr,
// which carries the recursion guard a Rust `{:?}` walk does not.
if let Some(param_spec) = zelf.__origin__.downcast_ref::<ParamSpec>() {
return Ok(format!("{}.args", param_spec.__name__().str_utf8(vm)?));
return Ok(format!("{}.args", param_spec.name.str_utf8(vm)?));
}
Ok(format!("{}.args", zelf.__origin__.repr(vm)?))
}
@@ -955,6 +908,7 @@ pub(crate) mod typevar {
#[pyclass(name = "ParamSpecKwargs", module = "typing")]
#[derive(Debug, PyPayload)]
pub struct ParamSpecKwargs {
#[pymember]
__origin__: PyObjectRef,
}
@@ -964,11 +918,6 @@ pub(crate) mod typevar {
fn __mro_entries__(&self, _object: PyObjectRef, vm: &VirtualMachine) -> PyResult {
Err(vm.new_type_error("Cannot subclass an instance of ParamSpecKwargs"))
}
#[pygetset]
fn __origin__(&self) -> PyObjectRef {
self.__origin__.clone()
}
}
impl Constructor for ParamSpecKwargs {
@@ -986,7 +935,7 @@ pub(crate) mod typevar {
// A ParamSpec origin is named; anything else is shown by its repr,
// which carries the recursion guard a Rust `{:?}` walk does not.
if let Some(param_spec) = zelf.__origin__.downcast_ref::<ParamSpec>() {
return Ok(format!("{}.kwargs", param_spec.__name__().str_utf8(vm)?));
return Ok(format!("{}.kwargs", param_spec.name.str_utf8(vm)?));
}
Ok(format!("{}.kwargs", zelf.__origin__.repr(vm)?))
}
+1 -1
View File
@@ -599,7 +599,7 @@ pub(crate) fn hackcheck_setattro(
obj_cls.slot_name()
)));
}
base = b.base.deref().map(|cls| cls.to_owned());
base = b.base.load_owned();
}
Ok(())
}
+10 -14
View File
@@ -1,5 +1,5 @@
use crate::{
AsObject, PyObject, PyResult, VirtualMachine,
AsObject, PyObject,
builtins::{
PyByteArray, PyBytes, PyCapsule, PyComplex, PyDict, PyDictRef, PyEllipsis, PyFloat,
PyFrozenSet, PyInt, PyIntRef, PyList, PyListRef, PyNone, PyNotImplemented, PyStr,
@@ -7,8 +7,8 @@ use crate::{
bool_::PyBool,
code::{self, PyCode},
descriptor::{
MemberAccess, MemberKind, MemberSetterFunc, PY_READONLY, PyDescriptorOwned,
PyMemberDef, PyMemberDescriptor,
MemberAccess, MemberKind, PY_READONLY, PyDescriptorOwned, PyMemberDef,
PyMemberDescriptor,
},
getset::PyGetSet,
object, pystr,
@@ -661,12 +661,11 @@ impl Context {
&self,
name: &str,
kind: MemberKind,
getter: fn(&VirtualMachine, PyObjectRef) -> PyResult,
setter: MemberSetterFunc,
offset: isize,
flags: i32,
class: &'static Py<PyType>,
doc: Option<&str>,
) -> PyRef<PyMemberDescriptor> {
let flags = if setter.is_none() { PY_READONLY } else { 0 };
let member_descriptor = PyMemberDescriptor {
common: PyDescriptorOwned {
typ: class.to_owned(),
@@ -676,14 +675,11 @@ impl Context {
member: PyMemberDef {
name: name.to_owned(),
kind,
offset: 0,
offset,
flags,
doc: doc.map(str::to_owned),
},
access: MemberAccess::Func {
get: getter,
set: setter,
},
access: MemberAccess::Offset,
};
member_descriptor.into_ref(self)
}
@@ -721,7 +717,7 @@ impl Context {
where
F: IntoPyGetterFunc<T>,
{
let getset = PyGetSet::new(name, class).with_get(f);
let getset = PyGetSet::new(name, class, self).with_get(f);
PyRef::new_ref(getset, self.types.getset_type.to_owned(), None)
}
@@ -736,7 +732,7 @@ impl Context {
G: IntoPyGetterFunc<T>,
S: IntoPySetterFunc<U>,
{
let getset = PyGetSet::new(name, class).with_get(g).with_set(s);
let getset = PyGetSet::new(name, class, self).with_get(g).with_set(s);
PyRef::new_ref(getset, self.types.getset_type.to_owned(), None)
}
@@ -752,7 +748,7 @@ impl Context {
G: IntoPyGetterFunc<T>,
S: IntoPySetterFunc<U>,
{
let getset = PyGetSet::new(name, class).with_get(g).with_set(s);
let getset = PyGetSet::new(name, class, self).with_get(g).with_set(s);
PyRef::new_ref(getset, self.types.getset_type.to_owned(), None)
}
+3 -1
View File
@@ -1627,7 +1627,9 @@ impl VirtualMachine {
// Create a function object for module code, similar to PyEval_EvalCode
let mut func = PyFunction::new(code, scope.globals.clone(), self)?;
func.closure = closure;
if let Some(closure) = closure {
func.closure = Some(closure);
}
let func = func.into_ref(&self.ctx);
func.invoke_with_locals(FuncArgs::default(), scope.locals, self)
}
+1 -1
View File
@@ -1110,7 +1110,7 @@ pub fn get_all_current_exceptions(vm: &VirtualMachine) -> Vec<(u64, Option<PyBas
let registry = vm.state.thread_frames.lock();
registry
.iter()
.map(|(id, slot)| (*id, slot.exception.to_owned()))
.map(|(id, slot)| (*id, slot.exception.load_owned()))
.collect()
}
+41 -11
View File
@@ -14,8 +14,9 @@ use rustpython_compiler::{CompileError, ParseError, is_blank_python_source};
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
builtins::{
PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef, PyMemoryError, PyModule,
PyOSError, PyStopIteration, PyStrRef, PySystemExit, PyType, PyTypeRef,
PyAttributeError, PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef,
PyImportError, PyMemoryError, PyModule, PyNameError, PyOSError, PyStopIteration, PyStrRef,
PySyntaxError, PySystemExit, PyType, PyTypeRef,
builtin_func::PyNativeFunction,
descriptor::PyMethodDescriptor,
tuple::{IntoPyTuple, PyTupleRef},
@@ -538,14 +539,18 @@ impl VirtualMachine {
}
pub fn new_name_error(&self, msg: impl Into<Wtf8Buf>, name: PyStrRef) -> PyBaseExceptionRef {
let name_error_type = self.ctx.exceptions.name_error.to_owned();
let name_error = self.new_exception_msg(name_error_type, msg.into());
let name_error = self
.new_payload_exception::<PyNameError>(
self.ctx.exceptions.name_error.to_owned(),
vec![self.ctx.new_str(msg.into()).into()].into(),
)
.expect("NameError construction from internal args is infallible");
set_attrs!(
name_error.as_object(), self, unwrap,
"name" => name,
);
name_error
name_error.upcast()
}
/// The error a call that passed the wrong number of arguments gets. The
@@ -961,7 +966,13 @@ impl VirtualMachine {
_ => false,
};
let syntax_error = self.new_exception_msg(syntax_error_type, msg.into());
let syntax_error: PyBaseExceptionRef = self
.new_payload_exception::<PySyntaxError>(
syntax_error_type,
vec![self.ctx.new_str(msg).into()].into(),
)
.expect("SyntaxError construction from internal args is infallible")
.upcast();
let (lineno_raw, offset_raw) = error.python_location();
let lineno = self.ctx.new_int(lineno_raw);
@@ -1043,14 +1054,18 @@ impl VirtualMachine {
msg: impl Into<Wtf8Buf>,
name: impl Into<PyStrRef>,
) -> PyBaseExceptionRef {
let import_error = self.ctx.exceptions.import_error.to_owned();
let exc = self.new_exception_msg(import_error, msg.into());
let exc = self
.new_payload_exception::<PyImportError>(
self.ctx.exceptions.import_error.to_owned(),
vec![self.ctx.new_str(msg.into()).into()].into(),
)
.expect("ImportError construction from internal args is infallible");
set_attrs!(
exc.as_object(), self, unwrap,
"name" => name.into(),
);
exc
exc.upcast()
}
pub fn new_system_exit(&self, args: FuncArgs) -> PyBaseExceptionRef {
@@ -1138,7 +1153,15 @@ impl VirtualMachine {
define_exception_fn!(fn new_lookup_error, lookup_error, LookupError);
define_exception_fn!(fn new_eof_error, eof_error, EOFError);
define_exception_fn!(fn new_attribute_error, attribute_error, AttributeError);
pub fn new_attribute_error(&self, msg: impl Into<Wtf8Buf>) -> PyBaseExceptionRef {
self.new_payload_exception::<PyAttributeError>(
self.ctx.exceptions.attribute_error.to_owned(),
vec![self.ctx.new_str(msg.into()).into()].into(),
)
.expect("AttributeError construction from internal args is infallible")
.upcast()
}
define_exception_fn!(fn new_type_error, type_error, TypeError);
define_exception_fn!(fn new_system_error, system_error, SystemError);
@@ -1158,7 +1181,14 @@ impl VirtualMachine {
define_exception_fn!(fn new_python_finalization_error, python_finalization_error, PythonFinalizationError);
define_exception_fn!(fn new_memory_error, memory_error, MemoryError);
define_exception_fn!(fn new_assertion_error, assertion_error, AssertionError);
define_exception_fn!(fn new_unbound_local_error, unbound_local_error, UnboundLocalError);
pub fn new_unbound_local_error(&self, msg: impl Into<Wtf8Buf>) -> PyBaseExceptionRef {
self.new_payload_exception::<PyNameError>(
self.ctx.exceptions.unbound_local_error.to_owned(),
vec![self.ctx.new_str(msg.into()).into()].into(),
)
.expect("UnboundLocalError construction from internal args is infallible")
.upcast()
}
}
#[cfg(feature = "parser")]