mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Give heap types a dict namespace (#8606)
Replace `PyType::attributes` (`PyRwLock<PyAttributes>`) with a `TypeNamespace` enum. A type created while an interpreter is running holds a `PyDict`; the types `Context::genesis` and `PyType::new_static` build keep the interned-key `IndexMap`, since hashing a string needs a VM. `type.__new__` binds `__classdictcell__` to the type's own namespace rather than the namespace dict passed to it, so an annotation scope reads attribute changes made after the class body ran. Removes the `expectedFailure` on `test_type_params.TypeParamsClassScopeTest.test_modified_later`. `PyGetSet` holds its class as a `PyRef<PyType>` instead of a non-owning `PointerSlot`, and traverses it, because a namespace can outlive the type it belongs to. `PointerSlot` and the `unsafe` on `Context::new_getset` are gone. `subs_parameters` raises `TypeError` when `__typing_subst__` returns a non-tuple in an unpack position, using the new `PyType::fully_qualified_name` for the message. Removes the `expectedFailure` on `test_typing.GenericTests.test_return_non_tuple_while_unpacking`. Assisted-by: Claude
This commit is contained in:
1 parent
fab3fdb704
commit
0ae835a073
19 files changed
+429
-289
No files matched your search
Vendored
-1
@@ -674,7 +674,6 @@ class TypeParamsClassScopeTest(unittest.TestCase):
|
||||
self.assertEqual(T.__bound__, "global")
|
||||
self.assertEqual(U.__bound__, "class")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <class 'int'> is not <class 'float'>
|
||||
def test_modified_later(self):
|
||||
class X:
|
||||
T = int
|
||||
|
||||
Vendored
-1
@@ -5887,7 +5887,6 @@ class GenericTests(BaseTestCase):
|
||||
with self.assertRaises(TypeError):
|
||||
a[int]
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ".+__typing_subst__.+tuple.+int.*" does not match "'TypeAliasType' object is not subscriptable"
|
||||
def test_return_non_tuple_while_unpacking(self):
|
||||
# GH-138497: GenericAlias objects didn't ensure that __typing_subst__ actually
|
||||
# returned a tuple
|
||||
|
||||
@@ -763,7 +763,7 @@ impl ModuleItem for ClassItem {
|
||||
// module resolution, e.g. TypeAliasType)
|
||||
{
|
||||
let module_key = rustpython_vm::identifier!(ctx, __module__);
|
||||
let has_module_getset = new_class.attributes.read()
|
||||
let has_module_getset = new_class.attributes
|
||||
.get(module_key)
|
||||
.is_some_and(|v| v.downcastable::<rustpython_vm::builtins::PyGetSet>());
|
||||
if !has_module_getset {
|
||||
@@ -857,7 +857,7 @@ impl ModuleItem for StructSequenceItem {
|
||||
let new_class = <#pytype_ident as ::rustpython_vm::class::PyClassImpl>::make_static_type();
|
||||
{
|
||||
let module_key = rustpython_vm::identifier!(ctx, __module__);
|
||||
let has_module_getset = new_class.attributes.read()
|
||||
let has_module_getset = new_class.attributes
|
||||
.get(module_key)
|
||||
.is_some_and(|v| v.downcastable::<rustpython_vm::builtins::PyGetSet>());
|
||||
if !has_module_getset {
|
||||
|
||||
@@ -173,7 +173,7 @@ mod _pyexpat {
|
||||
|
||||
#[extend_class]
|
||||
fn extend_class_with_fields(ctx: &Context, class: &'static Py<PyType>) {
|
||||
let mut attributes = class.attributes.write();
|
||||
let attributes = &class.attributes;
|
||||
|
||||
create_property!(ctx, attributes, "StartElementHandler", class, start_element);
|
||||
create_property!(ctx, attributes, "EndElementHandler", class, end_element);
|
||||
|
||||
@@ -551,12 +551,15 @@ pub(crate) fn subs_parameters(
|
||||
};
|
||||
|
||||
if unpack {
|
||||
if let Ok(tuple) = substituted_arg.try_to_ref::<PyTuple>(vm) {
|
||||
for elem in tuple {
|
||||
new_args.push(elem.clone());
|
||||
}
|
||||
} else {
|
||||
new_args.push(substituted_arg);
|
||||
let tuple = substituted_arg.try_to_ref::<PyTuple>(vm).map_err(|_| {
|
||||
vm.new_type_error(format!(
|
||||
"expected __typing_subst__ of {} objects to return a tuple, not {}",
|
||||
arg.class().fully_qualified_name(vm),
|
||||
substituted_arg.class().fully_qualified_name(vm),
|
||||
))
|
||||
})?;
|
||||
for elem in tuple {
|
||||
new_args.push(elem.clone());
|
||||
}
|
||||
} else {
|
||||
new_args.push(substituted_arg);
|
||||
|
||||
@@ -2,17 +2,19 @@
|
||||
|
||||
use super::PyType;
|
||||
use crate::{
|
||||
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
|
||||
builtins::type_::PointerSlot,
|
||||
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
class::PyClassImpl,
|
||||
function::{IntoPyGetterFunc, IntoPySetterFunc, PyGetterFunc, PySetterFunc, PySetterValue},
|
||||
object::{Traverse, TraverseFn},
|
||||
types::{GetDescriptor, Representable},
|
||||
};
|
||||
|
||||
#[pyclass(module = false, name = "getset_descriptor")]
|
||||
#[pyclass(module = false, name = "getset_descriptor", traverse = "manual")]
|
||||
pub struct PyGetSet {
|
||||
name: String,
|
||||
class: PointerSlot<Py<PyType>>, // A class type freed before getset is non-sense.
|
||||
/// `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.
|
||||
class: PyRef<PyType>,
|
||||
getter: Option<PyGetterFunc>,
|
||||
setter: Option<PySetterFunc>,
|
||||
// doc: Option<String>,
|
||||
@@ -38,6 +40,13 @@ impl core::fmt::Debug for PyGetSet {
|
||||
}
|
||||
}
|
||||
|
||||
// Only `class` is traced: the getter and setter closures are plain functions.
|
||||
unsafe impl Traverse for PyGetSet {
|
||||
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
|
||||
self.class.traverse(tracer_fn);
|
||||
}
|
||||
}
|
||||
|
||||
impl PyPayload for PyGetSet {
|
||||
#[inline]
|
||||
fn class(ctx: &Context) -> &'static Py<PyType> {
|
||||
@@ -70,10 +79,10 @@ impl GetDescriptor for PyGetSet {
|
||||
|
||||
impl PyGetSet {
|
||||
#[must_use]
|
||||
pub fn new(name: &str, class: &'static Py<PyType>) -> Self {
|
||||
pub fn new(name: &str, class: &Py<PyType>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
class: PointerSlot::from(class),
|
||||
class: class.to_owned(),
|
||||
getter: None,
|
||||
setter: None,
|
||||
}
|
||||
@@ -128,26 +137,22 @@ impl PyGetSet {
|
||||
|
||||
#[pygetset]
|
||||
fn __qualname__(&self) -> String {
|
||||
format!(
|
||||
"{}.{}",
|
||||
unsafe { self.class.borrow_static() }.slot_name(),
|
||||
self.name.clone()
|
||||
)
|
||||
format!("{}.{}", self.class.slot_name(), self.name.clone())
|
||||
}
|
||||
|
||||
#[pymember]
|
||||
fn __objclass__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
|
||||
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
|
||||
Ok(unsafe { zelf.class.borrow_static() }.to_owned().into())
|
||||
Ok(zelf.class.clone().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Representable for PyGetSet {
|
||||
#[inline]
|
||||
fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
|
||||
let class = unsafe { zelf.class.borrow_static() };
|
||||
let class = &zelf.class;
|
||||
// Special case for object type
|
||||
if core::ptr::eq(class, vm.ctx.types.object_type) {
|
||||
if class.is(vm.ctx.types.object_type) {
|
||||
Ok(format!("<attribute '{}'>", zelf.name))
|
||||
} else {
|
||||
Ok(format!(
|
||||
|
||||
@@ -95,13 +95,24 @@ impl PyMappingProxy {
|
||||
|
||||
fn get_inner(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> {
|
||||
match &self.mapping {
|
||||
MappingProxyInner::Class(class) => Ok(key
|
||||
.as_interned_str(vm)
|
||||
.and_then(|key| class.attributes.read().get(key).cloned())),
|
||||
MappingProxyInner::Class(class) => Self::class_get(class, &key, vm),
|
||||
MappingProxyInner::Mapping(mapping) => mapping.mapping().subscript(&*key, vm).map(Some),
|
||||
}
|
||||
}
|
||||
|
||||
fn class_get(
|
||||
class: &Py<PyType>,
|
||||
key: &PyObject,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Option<PyObjectRef>> {
|
||||
match class.attributes.as_dict() {
|
||||
Some(dict) => dict.get_item_opt(key, vm),
|
||||
None => Ok(key
|
||||
.as_interned_str(vm)
|
||||
.and_then(|key| class.attributes.get(key))),
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn get(
|
||||
&self,
|
||||
@@ -124,15 +135,22 @@ impl PyMappingProxy {
|
||||
|
||||
fn _contains(&self, key: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
|
||||
match &self.mapping {
|
||||
MappingProxyInner::Class(class) => Ok(key
|
||||
.as_interned_str(vm)
|
||||
.is_some_and(|key| class.attributes.read().contains_key(key))),
|
||||
MappingProxyInner::Class(class) => Ok(Self::class_contains(class, key, vm)),
|
||||
MappingProxyInner::Mapping(mapping) => {
|
||||
mapping.obj().sequence_unchecked().contains(key, vm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn class_contains(class: &Py<PyType>, key: &PyObject, vm: &VirtualMachine) -> bool {
|
||||
match class.attributes.as_dict() {
|
||||
Some(dict) => dict.contains_key(key, vm),
|
||||
None => key
|
||||
.as_interned_str(vm)
|
||||
.is_some_and(|key| class.attributes.contains(key)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn __contains__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
|
||||
self._contains(&key, vm)
|
||||
}
|
||||
@@ -140,12 +158,17 @@ impl PyMappingProxy {
|
||||
fn to_object(&self, vm: &VirtualMachine) -> PyResult {
|
||||
Ok(match &self.mapping {
|
||||
MappingProxyInner::Mapping(d) => d.as_ref().to_owned(),
|
||||
MappingProxyInner::Class(c) => {
|
||||
PyDict::from_attributes(c.attributes.read().clone(), vm)?.to_pyobject(vm)
|
||||
}
|
||||
MappingProxyInner::Class(c) => Self::class_to_dict(c, vm)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn class_to_dict(class: &Py<PyType>, vm: &VirtualMachine) -> PyResult {
|
||||
if let Some(dict) = class.attributes.as_dict() {
|
||||
return Ok(dict.copy().to_pyobject(vm));
|
||||
}
|
||||
Ok(PyDict::from_attributes(class.attributes.attributes(&vm.ctx), vm)?.to_pyobject(vm))
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
pub fn items(&self, vm: &VirtualMachine) -> PyResult {
|
||||
let obj = self.to_object(vm)?;
|
||||
@@ -170,9 +193,7 @@ impl PyMappingProxy {
|
||||
MappingProxyInner::Mapping(d) => {
|
||||
vm.call_method(d.obj(), identifier!(vm, copy).as_str(), ())
|
||||
}
|
||||
MappingProxyInner::Class(c) => {
|
||||
Ok(PyDict::from_attributes(c.attributes.read().clone(), vm)?.to_pyobject(vm))
|
||||
}
|
||||
MappingProxyInner::Class(c) => Self::class_to_dict(c, vm),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine)
|
||||
}
|
||||
} else {
|
||||
let weakref_name = vm.ctx.intern_str("__weakref__");
|
||||
obj.class().attributes.read().contains_key(weakref_name)
|
||||
obj.class().attributes.contains(weakref_name)
|
||||
};
|
||||
if has_weakref {
|
||||
basicsize += core::mem::size_of::<PyObjectRef>();
|
||||
|
||||
+313
-152
@@ -1,6 +1,6 @@
|
||||
use super::{
|
||||
PyClassMethod, PyDictRef, PyList, PyStaticMethod, PyStr, PyStrInterned, PyStrRef, PyTupleRef,
|
||||
PyUtf8StrRef, PyWeak, mappingproxy::PyMappingProxy, object, union_,
|
||||
PyClassMethod, PyDict, PyDictRef, PyList, PyStaticMethod, PyStr, PyStrInterned, PyStrRef,
|
||||
PyTupleRef, PyUtf8StrRef, PyWeak, mappingproxy::PyMappingProxy, object, union_,
|
||||
};
|
||||
use crate::{
|
||||
AsObject, Context, Py, PyAtomicRef, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
|
||||
@@ -33,7 +33,6 @@ use core::{
|
||||
borrow::Borrow,
|
||||
ops::Deref,
|
||||
pin::Pin,
|
||||
ptr::NonNull,
|
||||
sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, Ordering},
|
||||
};
|
||||
use indexmap::{IndexMap, map::Entry};
|
||||
@@ -49,7 +48,7 @@ pub struct PyType {
|
||||
pub bases: PyRwLock<Vec<PyTypeRef>>,
|
||||
pub mro: PyRwLock<Vec<PyTypeRef>>,
|
||||
pub subclasses: PyRwLock<Vec<PyRef<PyWeak>>>,
|
||||
pub attributes: PyRwLock<PyAttributes>,
|
||||
pub attributes: TypeNamespace,
|
||||
pub slots: PyTypeSlots,
|
||||
pub heaptype_ext: Option<Pin<Box<HeapTypeExt>>>,
|
||||
/// Type version tag for inline caching. 0 means unassigned/invalidated.
|
||||
@@ -245,11 +244,7 @@ unsafe impl crate::object::Traverse for PyType {
|
||||
self.bases.traverse(tracer_fn);
|
||||
self.mro.traverse(tracer_fn);
|
||||
self.subclasses.traverse(tracer_fn);
|
||||
self.attributes
|
||||
.read_recursive()
|
||||
.iter()
|
||||
.map(|(_, v)| v.traverse(tracer_fn))
|
||||
.count();
|
||||
self.attributes.traverse(tracer_fn);
|
||||
if let Some(ext) = self.heaptype_ext.as_ref() {
|
||||
ext.specialization_cache.traverse(tracer_fn);
|
||||
}
|
||||
@@ -276,11 +271,7 @@ unsafe impl crate::object::Traverse for PyType {
|
||||
out.push(weak.into());
|
||||
}
|
||||
}
|
||||
if let Some(mut guard) = self.attributes.try_write() {
|
||||
for (_, val) in guard.drain(..) {
|
||||
out.push(val);
|
||||
}
|
||||
}
|
||||
self.attributes.drain_into(out);
|
||||
if let Some(ext) = self.heaptype_ext.as_ref() {
|
||||
ext.specialization_cache.clear_into(out);
|
||||
}
|
||||
@@ -380,37 +371,6 @@ impl TypeSpecializationCache {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PointerSlot<T>(NonNull<T>);
|
||||
|
||||
unsafe impl<T> Sync for PointerSlot<T> {}
|
||||
unsafe impl<T> Send for PointerSlot<T> {}
|
||||
|
||||
impl<T> PointerSlot<T> {
|
||||
pub(crate) const unsafe fn borrow_static(self) -> &'static T {
|
||||
unsafe { self.0.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for PointerSlot<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for PointerSlot<T> {}
|
||||
|
||||
impl<T> From<&'static T> for PointerSlot<T> {
|
||||
fn from(x: &'static T) -> Self {
|
||||
Self(NonNull::from(x))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for PointerSlot<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
unsafe { self.0.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
pub type PyTypeRef = PyRef<PyType>;
|
||||
|
||||
cfg_select! {
|
||||
@@ -433,6 +393,195 @@ unsafe impl Traverse for PyAttributes {
|
||||
}
|
||||
}
|
||||
|
||||
/// A type's namespace, `tp_dict`.
|
||||
///
|
||||
/// Types created while an interpreter is running own a real dict, so the object
|
||||
/// `__classdictcell__` hands to annotation scopes is the type's own storage.
|
||||
/// The types `Context::genesis` builds cannot have one: hashing a string needs
|
||||
/// a VM and none exists yet, so they keep an interned-key map instead.
|
||||
pub enum TypeNamespace {
|
||||
Attributes(PyRwLock<PyAttributes>),
|
||||
Dict(PyDictRef),
|
||||
}
|
||||
|
||||
unsafe impl Traverse for TypeNamespace {
|
||||
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
|
||||
match self {
|
||||
Self::Attributes(attrs) => attrs.read_recursive().traverse(tracer_fn),
|
||||
Self::Dict(dict) => tracer_fn(dict.as_object()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TypeNamespace {
|
||||
fn default() -> Self {
|
||||
Self::Attributes(PyRwLock::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PyAttributes> for TypeNamespace {
|
||||
fn from(attrs: PyAttributes) -> Self {
|
||||
Self::Attributes(PyRwLock::new(attrs))
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeNamespace {
|
||||
/// The namespace as a dict, for the types that have one.
|
||||
pub const fn as_dict(&self) -> Option<&PyDictRef> {
|
||||
match self {
|
||||
Self::Attributes(_) => None,
|
||||
Self::Dict(dict) => Some(dict),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a dict-backed namespace holding `attrs`, or fall back to an
|
||||
/// interned-key map when no VM is running to hash the keys with.
|
||||
fn new(attrs: PyAttributes, ctx: &Context) -> Self {
|
||||
let built = crate::vm::thread::try_with_current_vm(|vm| {
|
||||
let dict = ctx.new_dict();
|
||||
for (key, value) in &attrs {
|
||||
dict.set_item(*key, value.clone(), vm)?;
|
||||
}
|
||||
PyResult::Ok(dict)
|
||||
});
|
||||
match built {
|
||||
Some(Ok(dict)) => Self::Dict(dict),
|
||||
_ => attrs.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &'static PyStrInterned) -> Option<PyObjectRef> {
|
||||
match self {
|
||||
Self::Attributes(attrs) => attrs.read().get(name).cloned(),
|
||||
Self::Dict(dict) => dict_get(dict, name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, name: &'static PyStrInterned) -> bool {
|
||||
match self {
|
||||
Self::Attributes(attrs) => attrs.read().contains_key(name),
|
||||
Self::Dict(dict) => {
|
||||
match crate::vm::thread::try_with_current_vm(|vm| dict.contains_key(name, vm)) {
|
||||
Some(found) => found,
|
||||
None => dict_get(dict, name).is_some(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bind `name` to `value`, dropping whatever it displaced.
|
||||
pub fn set(&self, name: &'static PyStrInterned, value: PyObjectRef) {
|
||||
match self {
|
||||
Self::Attributes(attrs) => {
|
||||
attrs.write().insert(name, value);
|
||||
}
|
||||
Self::Dict(dict) => {
|
||||
if let Some(Err(_)) | None =
|
||||
crate::vm::thread::try_with_current_vm(|vm| dict.set_item(name, value, vm))
|
||||
{
|
||||
debug_assert!(false, "type namespace write without a running VM");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bind `name` to `value` and hand back what it displaced, so the caller
|
||||
/// can decide where the old value is dropped.
|
||||
pub fn insert(&self, name: &'static PyStrInterned, value: PyObjectRef) -> Option<PyObjectRef> {
|
||||
match self {
|
||||
Self::Attributes(attrs) => attrs.write().insert(name, value),
|
||||
Self::Dict(dict) => {
|
||||
let previous = dict_get(dict, name);
|
||||
self.set(name, value);
|
||||
previous
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&self, name: &'static PyStrInterned) -> Option<PyObjectRef> {
|
||||
match self {
|
||||
Self::Attributes(attrs) => attrs.write().shift_remove(name),
|
||||
Self::Dict(dict) => {
|
||||
let previous = dict_get(dict, name)?;
|
||||
crate::vm::thread::try_with_current_vm(|vm| dict.del_item(name, vm).ok());
|
||||
Some(previous)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The namespace contents in insertion order.
|
||||
pub fn entries(&self) -> Vec<(PyObjectRef, PyObjectRef)> {
|
||||
match self {
|
||||
Self::Attributes(attrs) => attrs
|
||||
.read()
|
||||
.iter()
|
||||
.map(|(name, value)| ((*name).to_object(), value.clone()))
|
||||
.collect(),
|
||||
Self::Dict(dict) => dict.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The namespace keyed by interned name; dict keys that are not strings
|
||||
/// are left out, since `PyAttributes` has nowhere to put them.
|
||||
pub fn attributes(&self, ctx: &Context) -> PyAttributes {
|
||||
match self {
|
||||
Self::Attributes(attrs) => attrs.read().clone(),
|
||||
Self::Dict(dict) => dict
|
||||
.into_iter()
|
||||
.filter_map(|(key, value)| {
|
||||
let key = key.downcast_ref::<PyStr>()?;
|
||||
Some((ctx.intern_str(key.as_wtf8()), value))
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The interned names in the namespace; dict keys that are not strings are
|
||||
/// left out.
|
||||
pub fn interned_names(&self, ctx: &Context) -> Vec<&'static PyStrInterned> {
|
||||
match self {
|
||||
Self::Attributes(attrs) => attrs.read().keys().copied().collect(),
|
||||
Self::Dict(dict) => dict
|
||||
.into_iter()
|
||||
.filter_map(|(key, _)| {
|
||||
let key = key.downcast_ref::<PyStr>()?;
|
||||
Some(ctx.intern_str(key.as_wtf8()))
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Empty the namespace, handing the values to the caller. Used by tp_clear.
|
||||
fn drain_into(&mut self, out: &mut Vec<PyObjectRef>) {
|
||||
match self {
|
||||
Self::Attributes(attrs) => {
|
||||
if let Some(mut guard) = attrs.try_write() {
|
||||
out.extend(guard.drain(..).map(|(_, value)| value));
|
||||
}
|
||||
}
|
||||
Self::Dict(dict) => {
|
||||
out.extend((&**dict).into_iter().map(|(_, value)| value));
|
||||
PyDict::clear(dict);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Look a name up in a dict-backed namespace. Falls back to a scan when no VM
|
||||
/// is running, since hashing the key needs one.
|
||||
fn dict_get(dict: &Py<PyDict>, name: &'static PyStrInterned) -> Option<PyObjectRef> {
|
||||
match crate::vm::thread::try_with_current_vm(|vm| dict.get_item_opt(name, vm)) {
|
||||
Some(found) => found.ok().flatten(),
|
||||
None => dict
|
||||
.into_iter()
|
||||
.find(|(key, _)| {
|
||||
key.downcast_ref::<PyStr>()
|
||||
.is_some_and(|key| key.as_wtf8() == name.as_wtf8())
|
||||
})
|
||||
.map(|(_, value)| value),
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for PyType {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
core::fmt::Display::fmt(&self.name(), f)
|
||||
@@ -842,7 +991,7 @@ impl PyType {
|
||||
bases: PyRwLock::new(bases),
|
||||
mro: PyRwLock::new(mro),
|
||||
subclasses: PyRwLock::default(),
|
||||
attributes: PyRwLock::new(attrs),
|
||||
attributes: TypeNamespace::new(attrs, ctx),
|
||||
slots,
|
||||
heaptype_ext: Some(Pin::new(Box::new(heaptype_ext))),
|
||||
tp_version_tag: AtomicU32::new(0),
|
||||
@@ -904,7 +1053,7 @@ impl PyType {
|
||||
bases,
|
||||
mro: PyRwLock::new(mro),
|
||||
subclasses: PyRwLock::default(),
|
||||
attributes: PyRwLock::new(attrs),
|
||||
attributes: attrs.into(),
|
||||
slots,
|
||||
heaptype_ext: None,
|
||||
tp_version_tag: AtomicU32::new(0),
|
||||
@@ -956,13 +1105,13 @@ impl PyType {
|
||||
|
||||
// mro[0] is self, so skip it; self.attributes is checked separately below
|
||||
for cls in &self.mro.read()[1..] {
|
||||
for &name in cls.attributes.read().keys() {
|
||||
for name in cls.attributes.interned_names(ctx) {
|
||||
if name.as_bytes().starts_with(b"__") && name.as_bytes().ends_with(b"__") {
|
||||
slot_name_set.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &name in self.attributes.read().keys() {
|
||||
for name in self.attributes.interned_names(ctx) {
|
||||
if name.as_bytes().starts_with(b"__") && name.as_bytes().ends_with(b"__") {
|
||||
slot_name_set.insert(name);
|
||||
}
|
||||
@@ -1039,7 +1188,7 @@ impl PyType {
|
||||
// pointers in cache entries are nullified while the source objects
|
||||
// are still alive.
|
||||
self.modified();
|
||||
self.attributes.write().insert(attr_name, value);
|
||||
self.attributes.set(attr_name, value);
|
||||
}
|
||||
|
||||
/// Internal get_attr implementation for fast lookup on a class.
|
||||
@@ -1233,7 +1382,7 @@ impl PyType {
|
||||
}
|
||||
|
||||
pub fn get_direct_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
|
||||
self.attributes.read().get(attr_name).cloned()
|
||||
self.attributes.get(attr_name)
|
||||
}
|
||||
|
||||
/// find_name_in_mro with method cache (MCACHE).
|
||||
@@ -1251,8 +1400,8 @@ impl PyType {
|
||||
/// Raw MRO walk without cache.
|
||||
fn find_name_in_mro_uncached(&self, name: &'static PyStrInterned) -> Option<PyObjectRef> {
|
||||
for cls in self.mro.read().iter() {
|
||||
if let Some(value) = cls.attributes.read().get(name) {
|
||||
return Some(value.clone());
|
||||
if let Some(value) = cls.attributes.get(name) {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -1267,7 +1416,7 @@ impl PyType {
|
||||
pub fn get_super_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
|
||||
self.mro.read()[1..]
|
||||
.iter()
|
||||
.find_map(|class| class.attributes.read().get(attr_name).cloned())
|
||||
.find_map(|class| class.attributes.get(attr_name))
|
||||
}
|
||||
|
||||
/// Fast lookup for attribute existence on a class.
|
||||
@@ -1310,15 +1459,13 @@ impl PyType {
|
||||
self.find_name_in_mro(name).is_some()
|
||||
}
|
||||
|
||||
pub fn get_attributes(&self) -> PyAttributes {
|
||||
pub fn get_attributes(&self, ctx: &Context) -> PyAttributes {
|
||||
// Gather all members here:
|
||||
let mut attributes = PyAttributes::default();
|
||||
|
||||
// mro[0] is self, so we iterate through the entire MRO in reverse
|
||||
for bc in self.mro.read().iter().map(|cls| -> &Self { cls }).rev() {
|
||||
for (name, value) in bc.attributes.read().iter() {
|
||||
attributes.insert(name.to_owned(), value.clone());
|
||||
}
|
||||
attributes.extend(bc.attributes.attributes(ctx));
|
||||
}
|
||||
|
||||
attributes
|
||||
@@ -1361,6 +1508,21 @@ impl PyType {
|
||||
)
|
||||
}
|
||||
|
||||
/// The type's fully qualified name, the way the `%T` format code prints it:
|
||||
/// `module.qualname`, with a `builtins` or `__main__` module left off.
|
||||
pub fn fully_qualified_name(&self, vm: &VirtualMachine) -> String {
|
||||
let qualname = self.__qualname__(vm);
|
||||
let qualname = qualname
|
||||
.downcast_ref::<PyStr>()
|
||||
.and_then(|qualname| qualname.to_str())
|
||||
.map_or_else(|| self.name().to_string(), str::to_owned);
|
||||
let module = self.__module__(vm);
|
||||
match module.downcast_ref::<PyStr>().and_then(|m| m.to_str()) {
|
||||
Some("builtins" | "__main__") | None => qualname,
|
||||
Some(module) => format!("{module}.{qualname}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> BorrowedValue<'_, str> {
|
||||
self.name_inner(
|
||||
|name| name.rsplit_once('.').map_or(name, |(_, name)| name).into(),
|
||||
@@ -1729,26 +1891,23 @@ impl PyType {
|
||||
|
||||
let annotate_key = identifier!(vm, __annotate__);
|
||||
let annotate_func_key = identifier!(vm, __annotate_func__);
|
||||
let attrs = self.attributes.read();
|
||||
if let Some(annotate) = attrs.get(annotate_key).cloned() {
|
||||
if let Some(annotate) = self.attributes.get(annotate_key) {
|
||||
return Ok(annotate);
|
||||
}
|
||||
if let Some(annotate) = attrs.get(annotate_func_key).cloned() {
|
||||
if let Some(annotate) = self.attributes.get(annotate_func_key) {
|
||||
return Ok(annotate);
|
||||
}
|
||||
drop(attrs);
|
||||
|
||||
let none = vm.ctx.none();
|
||||
let (result, _prev) = Self::with_type_lock(vm, || {
|
||||
let mut attrs = self.attributes.write();
|
||||
if let Some(annotate) = attrs.get(annotate_key).cloned() {
|
||||
if let Some(annotate) = self.attributes.get(annotate_key) {
|
||||
return (annotate, None);
|
||||
}
|
||||
if let Some(annotate) = attrs.get(annotate_func_key).cloned() {
|
||||
if let Some(annotate) = self.attributes.get(annotate_func_key) {
|
||||
return (annotate, None);
|
||||
}
|
||||
self.modified_inner();
|
||||
let prev = attrs.insert(annotate_func_key, none.clone());
|
||||
let prev = self.attributes.insert(annotate_func_key, none.clone());
|
||||
(none, prev)
|
||||
});
|
||||
Ok(result)
|
||||
@@ -1776,14 +1935,16 @@ impl PyType {
|
||||
|
||||
let _prev_values = Self::with_type_lock(vm, || {
|
||||
self.modified_inner();
|
||||
let mut attrs = self.attributes.write();
|
||||
// Clear cached annotations only when setting to a new callable
|
||||
let removed = if !vm.is_none(&value) {
|
||||
attrs.swap_remove(identifier!(vm, __annotations_cache__))
|
||||
self.attributes
|
||||
.remove(identifier!(vm, __annotations_cache__))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let prev = attrs.insert(identifier!(vm, __annotate_func__), value);
|
||||
let prev = self
|
||||
.attributes
|
||||
.insert(identifier!(vm, __annotate_func__), value);
|
||||
(removed, prev)
|
||||
});
|
||||
|
||||
@@ -1794,8 +1955,7 @@ impl PyType {
|
||||
fn __annotations__(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
let annotations_key = identifier!(vm, __annotations__);
|
||||
let annotations_cache_key = identifier!(vm, __annotations_cache__);
|
||||
let attrs = self.attributes.read();
|
||||
if let Some(annotations) = attrs.get(annotations_key).cloned() {
|
||||
if let Some(annotations) = self.attributes.get(annotations_key) {
|
||||
// Ignore the __annotations__ descriptor stored on type itself.
|
||||
if !annotations.class().is(vm.ctx.types.getset_type) {
|
||||
if vm.is_none(&annotations)
|
||||
@@ -1810,7 +1970,7 @@ impl PyType {
|
||||
)));
|
||||
}
|
||||
}
|
||||
if let Some(annotations) = attrs.get(annotations_cache_key).cloned() {
|
||||
if let Some(annotations) = self.attributes.get(annotations_cache_key) {
|
||||
if vm.is_none(&annotations)
|
||||
|| annotations.class().is(vm.ctx.types.dict_type)
|
||||
|| self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE)
|
||||
@@ -1822,7 +1982,6 @@ impl PyType {
|
||||
self.name()
|
||||
)));
|
||||
}
|
||||
drop(attrs);
|
||||
|
||||
if !self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) {
|
||||
return Err(vm.new_attribute_error(format!(
|
||||
@@ -1848,17 +2007,18 @@ impl PyType {
|
||||
};
|
||||
|
||||
let (result, _prev) = Self::with_type_lock(vm, || {
|
||||
let mut attrs = self.attributes.write();
|
||||
if let Some(existing) = attrs.get(annotations_key).cloned()
|
||||
if let Some(existing) = self.attributes.get(annotations_key)
|
||||
&& !existing.class().is(vm.ctx.types.getset_type)
|
||||
{
|
||||
return (existing, None);
|
||||
}
|
||||
if let Some(existing) = attrs.get(annotations_cache_key).cloned() {
|
||||
if let Some(existing) = self.attributes.get(annotations_cache_key) {
|
||||
return (existing, None);
|
||||
}
|
||||
self.modified_inner();
|
||||
let prev = attrs.insert(annotations_cache_key, annotations.clone());
|
||||
let prev = self
|
||||
.attributes
|
||||
.insert(annotations_cache_key, annotations.clone());
|
||||
(annotations, prev)
|
||||
});
|
||||
Ok(result)
|
||||
@@ -1879,8 +2039,7 @@ impl PyType {
|
||||
|
||||
let _prev_values = Self::with_type_lock(vm, || {
|
||||
self.modified_inner();
|
||||
let mut attrs = self.attributes.write();
|
||||
let has_annotations = attrs.contains_key(identifier!(vm, __annotations__));
|
||||
let has_annotations = self.attributes.contains(identifier!(vm, __annotations__));
|
||||
|
||||
let mut prev = Vec::new();
|
||||
match value {
|
||||
@@ -1890,28 +2049,35 @@ impl PyType {
|
||||
} else {
|
||||
identifier!(vm, __annotations_cache__)
|
||||
};
|
||||
prev.extend(attrs.insert(key, value));
|
||||
prev.extend(self.attributes.insert(key, value));
|
||||
if has_annotations {
|
||||
prev.extend(attrs.swap_remove(identifier!(vm, __annotations_cache__)));
|
||||
prev.extend(
|
||||
self.attributes
|
||||
.remove(identifier!(vm, __annotations_cache__)),
|
||||
);
|
||||
}
|
||||
}
|
||||
crate::function::PySetterValue::Delete => {
|
||||
let removed = if has_annotations {
|
||||
attrs.swap_remove(identifier!(vm, __annotations__))
|
||||
self.attributes.remove(identifier!(vm, __annotations__))
|
||||
} else {
|
||||
attrs.swap_remove(identifier!(vm, __annotations_cache__))
|
||||
self.attributes
|
||||
.remove(identifier!(vm, __annotations_cache__))
|
||||
};
|
||||
if removed.is_none() {
|
||||
return Err(vm.new_attribute_error("__annotations__"));
|
||||
}
|
||||
prev.extend(removed);
|
||||
if has_annotations {
|
||||
prev.extend(attrs.swap_remove(identifier!(vm, __annotations_cache__)));
|
||||
prev.extend(
|
||||
self.attributes
|
||||
.remove(identifier!(vm, __annotations_cache__)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
prev.extend(attrs.swap_remove(identifier!(vm, __annotate_func__)));
|
||||
prev.extend(attrs.swap_remove(identifier!(vm, __annotate__)));
|
||||
prev.extend(self.attributes.remove(identifier!(vm, __annotate_func__)));
|
||||
prev.extend(self.attributes.remove(identifier!(vm, __annotate__)));
|
||||
Ok(prev)
|
||||
})?;
|
||||
|
||||
@@ -1921,9 +2087,7 @@ impl PyType {
|
||||
#[pygetset]
|
||||
pub fn __module__(&self, vm: &VirtualMachine) -> PyObjectRef {
|
||||
self.attributes
|
||||
.read()
|
||||
.get(identifier!(vm, __module__))
|
||||
.cloned()
|
||||
// We need to exclude this method from going into recursion:
|
||||
.filter(|found| !found.fast_isinstance(vm.ctx.types.getset_type))
|
||||
.unwrap_or_else(|| {
|
||||
@@ -1942,9 +2106,8 @@ impl PyType {
|
||||
self.check_set_special_type_attr(identifier!(vm, __module__), vm)?;
|
||||
let _prev_values = Self::with_type_lock(vm, || {
|
||||
self.modified_inner();
|
||||
let mut attributes = self.attributes.write();
|
||||
let removed = attributes.swap_remove(identifier!(vm, __firstlineno__));
|
||||
let prev = attributes.insert(identifier!(vm, __module__), value);
|
||||
let removed = self.attributes.remove(identifier!(vm, __firstlineno__));
|
||||
let prev = self.attributes.insert(identifier!(vm, __module__), value);
|
||||
(removed, prev)
|
||||
});
|
||||
Ok(())
|
||||
@@ -2056,10 +2219,9 @@ impl PyType {
|
||||
|
||||
#[pygetset]
|
||||
fn __type_params__(&self, vm: &VirtualMachine) -> PyTupleRef {
|
||||
let attrs = self.attributes.read();
|
||||
let key = identifier!(vm, __type_params__);
|
||||
if let Some(params) = attrs.get(&key)
|
||||
&& let Ok(tuple) = params.clone().downcast::<PyTuple>()
|
||||
if let Some(params) = self.attributes.get(key)
|
||||
&& let Ok(tuple) = params.downcast::<PyTuple>()
|
||||
{
|
||||
return tuple;
|
||||
}
|
||||
@@ -2079,7 +2241,7 @@ impl PyType {
|
||||
self.check_set_special_type_attr(key, vm)?;
|
||||
let _prev_value = Self::with_type_lock(vm, || {
|
||||
self.modified_inner();
|
||||
self.attributes.write().insert(key, val.into())
|
||||
self.attributes.insert(key, val.into())
|
||||
});
|
||||
}
|
||||
PySetterValue::Delete => {
|
||||
@@ -2091,7 +2253,7 @@ impl PyType {
|
||||
}
|
||||
let _prev_value = Self::with_type_lock(vm, || {
|
||||
self.modified_inner();
|
||||
self.attributes.write().shift_remove(&key)
|
||||
self.attributes.remove(key)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2441,28 +2603,32 @@ impl Constructor for PyType {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut attrs = typ.attributes.write();
|
||||
if let Some(cell) = attrs.get(identifier!(vm, __classcell__)) {
|
||||
let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| {
|
||||
vm.new_type_error(format!(
|
||||
"__classcell__ must be a nonlocal cell, not {}",
|
||||
cell.class().name()
|
||||
))
|
||||
})?;
|
||||
cell.set(Some(typ.clone().into()));
|
||||
attrs.shift_remove(identifier!(vm, __classcell__));
|
||||
}
|
||||
if let Some(cell) = attrs.get(identifier!(vm, __classdictcell__)) {
|
||||
let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| {
|
||||
vm.new_type_error(format!(
|
||||
"__classdictcell__ must be a nonlocal cell, not {}",
|
||||
cell.class().name()
|
||||
))
|
||||
})?;
|
||||
cell.set(Some(dict.clone().into()));
|
||||
attrs.shift_remove(identifier!(vm, __classdictcell__));
|
||||
}
|
||||
if let Some(cell) = typ.attributes.get(identifier!(vm, __classcell__)) {
|
||||
let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| {
|
||||
vm.new_type_error(format!(
|
||||
"__classcell__ must be a nonlocal cell, not {}",
|
||||
cell.class().name()
|
||||
))
|
||||
})?;
|
||||
cell.set(Some(typ.clone().into()));
|
||||
typ.attributes.remove(identifier!(vm, __classcell__));
|
||||
}
|
||||
if let Some(cell) = typ.attributes.get(identifier!(vm, __classdictcell__)) {
|
||||
let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| {
|
||||
vm.new_type_error(format!(
|
||||
"__classdictcell__ must be a nonlocal cell, not {}",
|
||||
cell.class().name()
|
||||
))
|
||||
})?;
|
||||
// Annotation scopes look names up here long after the class body ran,
|
||||
// so the cell gets the type's own namespace rather than the dict passed
|
||||
// to __new__: later attribute changes have to be visible through it.
|
||||
let namespace = typ
|
||||
.attributes
|
||||
.as_dict()
|
||||
.expect("a type built by type.__new__ has a dict namespace");
|
||||
cell.set(Some(namespace.clone().into()));
|
||||
typ.attributes.remove(identifier!(vm, __classdictcell__));
|
||||
}
|
||||
|
||||
// All *classes* should have a dict. Exceptions are *instances* of
|
||||
@@ -2482,14 +2648,12 @@ impl Constructor for PyType {
|
||||
.mro
|
||||
.read()
|
||||
.iter()
|
||||
.any(|base| base.attributes.read().contains_key(&__dict__));
|
||||
if !typ.attributes.read().contains_key(&__dict__) && !has_inherited_dict {
|
||||
unsafe {
|
||||
let descriptor =
|
||||
vm.ctx
|
||||
.new_getset("__dict__", &typ, subtype_get_dict, subtype_set_dict);
|
||||
typ.attributes.write().insert(__dict__, descriptor.into());
|
||||
}
|
||||
.any(|base| base.attributes.contains(__dict__));
|
||||
if !typ.attributes.contains(__dict__) && !has_inherited_dict {
|
||||
let descriptor =
|
||||
vm.ctx
|
||||
.new_getset("__dict__", &typ, subtype_get_dict, subtype_set_dict);
|
||||
typ.attributes.set(__dict__, descriptor.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2500,19 +2664,15 @@ impl Constructor for PyType {
|
||||
.mro
|
||||
.read()
|
||||
.iter()
|
||||
.any(|base| base.attributes.read().contains_key(&__weakref__));
|
||||
if !typ.attributes.read().contains_key(&__weakref__) && !has_inherited_weakref {
|
||||
unsafe {
|
||||
let descriptor = vm.ctx.new_getset(
|
||||
"__weakref__",
|
||||
&typ,
|
||||
subtype_get_weakref,
|
||||
subtype_set_weakref,
|
||||
);
|
||||
typ.attributes
|
||||
.write()
|
||||
.insert(__weakref__, descriptor.into());
|
||||
}
|
||||
.any(|base| base.attributes.contains(__weakref__));
|
||||
if !typ.attributes.contains(__weakref__) && !has_inherited_weakref {
|
||||
let descriptor = vm.ctx.new_getset(
|
||||
"__weakref__",
|
||||
&typ,
|
||||
subtype_get_weakref,
|
||||
subtype_set_weakref,
|
||||
);
|
||||
typ.attributes.set(__weakref__, descriptor.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2521,29 +2681,32 @@ impl Constructor for PyType {
|
||||
// which ensures every type has a __doc__ entry in its dict
|
||||
{
|
||||
let __doc__ = identifier!(vm, __doc__);
|
||||
if !typ.attributes.read().contains_key(&__doc__) {
|
||||
typ.attributes.write().insert(__doc__, vm.ctx.none());
|
||||
if !typ.attributes.contains(__doc__) {
|
||||
typ.attributes.set(__doc__, vm.ctx.none());
|
||||
}
|
||||
}
|
||||
|
||||
// avoid deadlock
|
||||
let attributes = typ
|
||||
.attributes
|
||||
.read()
|
||||
.iter()
|
||||
.entries()
|
||||
.into_iter()
|
||||
.filter_map(|(name, obj)| {
|
||||
vm.get_method(obj.clone(), identifier!(vm, __set_name__))
|
||||
.map(|res| res.map(|meth| (obj.clone(), name.to_owned(), meth)))
|
||||
.map(|res| res.map(|meth| (obj, name, meth)))
|
||||
})
|
||||
.collect::<PyResult<Vec<_>>>()?;
|
||||
for (obj, name, set_name) in attributes {
|
||||
let name_repr = name
|
||||
.str(vm)
|
||||
.map_or_else(|_| "?".to_owned(), |name| name.to_string());
|
||||
set_name.call((typ.clone(), name), vm).inspect_err(|e| {
|
||||
// PEP 678: Add a note to the original exception instead of wrapping it
|
||||
// (Python 3.12+, gh-77757)
|
||||
let note = format!(
|
||||
"Error calling __set_name__ on '{}' instance '{}' in '{}'",
|
||||
obj.class().name(),
|
||||
name,
|
||||
name_repr,
|
||||
typ.name()
|
||||
);
|
||||
// Ignore result - adding a note is best-effort, the original exception is what matters
|
||||
@@ -2721,18 +2884,16 @@ impl Py<PyType> {
|
||||
|
||||
let _prev_value = PyType::with_type_lock(vm, || {
|
||||
self.modified_inner();
|
||||
self.attributes
|
||||
.write()
|
||||
.insert(identifier!(vm, __doc__), value)
|
||||
self.attributes.insert(identifier!(vm, __doc__), value)
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn __dir__(&self) -> PyList {
|
||||
fn __dir__(&self, vm: &VirtualMachine) -> PyList {
|
||||
let attributes: Vec<PyObjectRef> = self
|
||||
.get_attributes()
|
||||
.get_attributes(&vm.ctx)
|
||||
.into_iter()
|
||||
.map(|(k, _)| k.to_object())
|
||||
.collect();
|
||||
@@ -2796,9 +2957,9 @@ impl SetAttr for PyType {
|
||||
zelf.modified_inner();
|
||||
|
||||
let prev_value = if let PySetterValue::Assign(value) = value {
|
||||
zelf.attributes.write().insert(attr_name, value)
|
||||
zelf.attributes.insert(attr_name, value)
|
||||
} else {
|
||||
let prev_value = zelf.attributes.write().shift_remove(attr_name); // TODO: swap_remove applicable?
|
||||
let prev_value = zelf.attributes.remove(attr_name);
|
||||
if prev_value.is_none() {
|
||||
return Err(vm.new_attribute_error(format!(
|
||||
"type object '{}' has no attribute '{}'",
|
||||
|
||||
@@ -46,7 +46,7 @@ pub fn add_operators(class: &'static Py<PyType>, ctx: &Context) {
|
||||
|
||||
// Check if attribute already exists in dict
|
||||
let attr_name = ctx.intern_str(def.name);
|
||||
if class.attributes.read().contains_key(attr_name) {
|
||||
if class.attributes.contains(attr_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ pub trait PyClassImpl: PyClassDef {
|
||||
// Only set __doc__ if it doesn't already exist (e.g., as a member descriptor)
|
||||
// This matches CPython's behavior in type_dict_set_doc
|
||||
let doc_attr_name = identifier!(ctx, __doc__);
|
||||
if class.attributes.read().get(doc_attr_name).is_none() {
|
||||
if class.attributes.get(doc_attr_name).is_none() {
|
||||
class.set_attr(doc_attr_name, ctx.new_str(doc).into());
|
||||
}
|
||||
}
|
||||
@@ -188,7 +188,6 @@ pub trait PyClassImpl: PyClassDef {
|
||||
// has an instance-level __module__ getset that should not be replaced)
|
||||
let has_getset = class
|
||||
.attributes
|
||||
.read()
|
||||
.get(module_key)
|
||||
.is_some_and(|v| v.downcastable::<crate::builtins::PyGetSet>());
|
||||
if !has_getset {
|
||||
|
||||
@@ -2773,7 +2773,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
|
||||
bases: PyRwLock::default(),
|
||||
mro: PyRwLock::default(),
|
||||
subclasses: PyRwLock::default(),
|
||||
attributes: PyRwLock::new(Default::default()),
|
||||
attributes: Default::default(),
|
||||
slots: PyType::make_slots(),
|
||||
heaptype_ext: None,
|
||||
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
|
||||
@@ -2784,7 +2784,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
|
||||
bases: PyRwLock::default(),
|
||||
mro: PyRwLock::default(),
|
||||
subclasses: PyRwLock::default(),
|
||||
attributes: PyRwLock::new(Default::default()),
|
||||
attributes: Default::default(),
|
||||
slots: object::PyBaseObject::make_slots(),
|
||||
heaptype_ext: None,
|
||||
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
|
||||
@@ -2885,7 +2885,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
|
||||
bases: PyRwLock::new(vec![object_type.clone()]),
|
||||
mro: PyRwLock::new(vec![object_type.clone()]),
|
||||
subclasses: PyRwLock::default(),
|
||||
attributes: PyRwLock::default(),
|
||||
attributes: Default::default(),
|
||||
slots: PyWeak::make_slots(),
|
||||
heaptype_ext: None,
|
||||
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
|
||||
|
||||
@@ -57,7 +57,7 @@ impl PyObjectRef {
|
||||
|
||||
// PyObject *PyObject_Dir(PyObject *o)
|
||||
pub fn dir(self, vm: &VirtualMachine) -> PyResult<PyList> {
|
||||
let attributes = self.class().get_attributes();
|
||||
let attributes = self.class().get_attributes(&vm.ctx);
|
||||
|
||||
let dict = PyDict::from_attributes(attributes, vm)?.into_ref(&vm.ctx);
|
||||
|
||||
|
||||
@@ -173,18 +173,10 @@ impl Initializer for PyCArrayType {
|
||||
new_type.check_not_initialized(vm)?;
|
||||
|
||||
// 1. Get _length_ from class dict first
|
||||
let direct_length = new_type
|
||||
.attributes
|
||||
.read()
|
||||
.get(vm.ctx.intern_str("_length_"))
|
||||
.cloned();
|
||||
let direct_length = new_type.attributes.get(vm.ctx.intern_str("_length_"));
|
||||
|
||||
// 2. Get _type_ from class dict first
|
||||
let direct_type = new_type
|
||||
.attributes
|
||||
.read()
|
||||
.get(vm.ctx.intern_str("_type_"))
|
||||
.cloned();
|
||||
let direct_type = new_type.attributes.get(vm.ctx.intern_str("_type_"));
|
||||
|
||||
// 3. Find parent StgInfo from MRO (for inheritance)
|
||||
// Note: PyType.mro does NOT include self, so no skip needed
|
||||
@@ -1158,44 +1150,34 @@ fn wchar_array_set_value(
|
||||
/// add_getset for c_char arrays - adds 'value' and 'raw' attributes
|
||||
/// add_getset((PyTypeObject*)self, CharArray_getsets)
|
||||
fn add_char_array_getsets(array_type: &Py<PyType>, vm: &VirtualMachine) {
|
||||
// SAFETY: getset is owned by array_type which outlives the getset
|
||||
let value_getset = unsafe {
|
||||
vm.ctx.new_getset(
|
||||
"value",
|
||||
array_type,
|
||||
char_array_get_value,
|
||||
char_array_set_value,
|
||||
)
|
||||
};
|
||||
let raw_getset = unsafe {
|
||||
vm.ctx
|
||||
.new_getset("raw", array_type, char_array_get_raw, char_array_set_raw)
|
||||
};
|
||||
let value_getset = vm.ctx.new_getset(
|
||||
"value",
|
||||
array_type,
|
||||
char_array_get_value,
|
||||
char_array_set_value,
|
||||
);
|
||||
let raw_getset = vm
|
||||
.ctx
|
||||
.new_getset("raw", array_type, char_array_get_raw, char_array_set_raw);
|
||||
|
||||
array_type
|
||||
.attributes
|
||||
.write()
|
||||
.insert(vm.ctx.intern_str("value"), value_getset.into());
|
||||
array_type
|
||||
.attributes
|
||||
.write()
|
||||
.insert(vm.ctx.intern_str("raw"), raw_getset.into());
|
||||
}
|
||||
|
||||
/// add_getset for c_wchar arrays - adds only 'value' attribute (no 'raw')
|
||||
fn add_wchar_array_getsets(array_type: &Py<PyType>, vm: &VirtualMachine) {
|
||||
// SAFETY: getset is owned by array_type which outlives the getset
|
||||
let value_getset = unsafe {
|
||||
vm.ctx.new_getset(
|
||||
"value",
|
||||
array_type,
|
||||
wchar_array_get_value,
|
||||
wchar_array_set_value,
|
||||
)
|
||||
};
|
||||
let value_getset = vm.ctx.new_getset(
|
||||
"value",
|
||||
array_type,
|
||||
wchar_array_get_value,
|
||||
wchar_array_set_value,
|
||||
);
|
||||
|
||||
array_type
|
||||
.attributes
|
||||
.write()
|
||||
.insert(vm.ctx.intern_str("value"), value_getset.into());
|
||||
}
|
||||
@@ -626,7 +626,6 @@ impl SetAttr for PyCStructType {
|
||||
// Set the _fields_ attribute on the type
|
||||
pytype
|
||||
.attributes
|
||||
.write()
|
||||
.insert(vm.ctx.intern_str("_fields_"), fields_value);
|
||||
return Ok(());
|
||||
}
|
||||
@@ -644,9 +643,9 @@ impl SetAttr for PyCStructType {
|
||||
|
||||
// Store in type's attributes dict
|
||||
if let PySetterValue::Assign(value) = value {
|
||||
pytype.attributes.write().insert(attr_name_interned, value);
|
||||
pytype.attributes.set(attr_name_interned, value);
|
||||
} else {
|
||||
let prev = pytype.attributes.write().shift_remove(attr_name_interned);
|
||||
let prev = pytype.attributes.remove(attr_name_interned);
|
||||
if prev.is_none() {
|
||||
return Err(vm.new_attribute_error(format!(
|
||||
"type object '{}' has no attribute '{}'",
|
||||
|
||||
@@ -512,13 +512,10 @@ impl SetAttr for PyCUnionType {
|
||||
// Store in type's attributes dict
|
||||
match &value {
|
||||
PySetterValue::Assign(v) => {
|
||||
pytype
|
||||
.attributes
|
||||
.write()
|
||||
.insert(attr_name_interned, v.clone());
|
||||
pytype.attributes.set(attr_name_interned, v.clone());
|
||||
}
|
||||
PySetterValue::Delete => {
|
||||
let prev = pytype.attributes.write().shift_remove(attr_name_interned);
|
||||
let prev = pytype.attributes.remove(attr_name_interned);
|
||||
if prev.is_none() {
|
||||
return Err(vm.new_attribute_error(format!(
|
||||
"type object '{}' has no attribute '{}'",
|
||||
|
||||
@@ -1333,15 +1333,11 @@ pub mod sys {
|
||||
return Err(vm.new_type_error("argument is immutable"));
|
||||
}
|
||||
|
||||
let mut attributes = type_obj.attributes.write();
|
||||
|
||||
// Remove __dict__ descriptor if present
|
||||
attributes.swap_remove(identifier!(vm, __dict__));
|
||||
type_obj.attributes.remove(identifier!(vm, __dict__));
|
||||
|
||||
// Remove __weakref__ descriptor if present
|
||||
attributes.swap_remove(identifier!(vm, __weakref__));
|
||||
|
||||
drop(attributes);
|
||||
type_obj.attributes.remove(identifier!(vm, __weakref__));
|
||||
|
||||
// Update slots to notify subclasses and recalculate cached values
|
||||
type_obj.update_slot::<true>(identifier!(vm, __dict__), &vm.ctx);
|
||||
|
||||
+18
-31
@@ -709,7 +709,7 @@ impl PyType {
|
||||
};
|
||||
|
||||
// Skip if subclass has its own definition for this attribute
|
||||
if subclass.attributes.read().contains_key(name) {
|
||||
if subclass.attributes.contains(name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -780,9 +780,8 @@ impl PyType {
|
||||
// methods (__setitem__ and __delitem__). If any of those methods
|
||||
// is defined, we must use the wrapper to ensure Python method calls.
|
||||
let has_own = {
|
||||
let guard = self.attributes.read();
|
||||
// Check the current method name
|
||||
let mut result = guard.contains_key(name);
|
||||
let mut result = self.attributes.contains(name);
|
||||
// For ass_item/ass_subscript slots, also check the paired method
|
||||
// (__setitem__ and __delitem__ share the same slot)
|
||||
if !result
|
||||
@@ -791,7 +790,8 @@ impl PyType {
|
||||
{
|
||||
let setitem = ctx.intern_str("__setitem__");
|
||||
let delitem = ctx.intern_str("__delitem__");
|
||||
result = guard.contains_key(setitem) || guard.contains_key(delitem);
|
||||
result = self.attributes.contains(setitem)
|
||||
|| self.attributes.contains(delitem);
|
||||
}
|
||||
result
|
||||
};
|
||||
@@ -836,11 +836,11 @@ impl PyType {
|
||||
SlotAccessor::TpHash => {
|
||||
// Special handling for __hash__ = None
|
||||
if ADD {
|
||||
let method = self.attributes.read().get(name).cloned().or_else(|| {
|
||||
let method = self.attributes.get(name).or_else(|| {
|
||||
self.mro
|
||||
.read()
|
||||
.iter()
|
||||
.find_map(|cls| cls.attributes.read().get(name).cloned())
|
||||
.find_map(|cls| cls.attributes.get(name))
|
||||
});
|
||||
|
||||
if method.as_ref().is_some_and(|m| m.is(&ctx.none)) {
|
||||
@@ -892,13 +892,13 @@ impl PyType {
|
||||
// builtin __new__ entry (or no entry at all) means the slot
|
||||
// is inherited from the solid base, matching update_one_slot's
|
||||
// tp_new special case over the tp_base-inherited value.
|
||||
let needs_wrapper = if ADD && self.attributes.read().contains_key(name) {
|
||||
let needs_wrapper = if ADD && self.attributes.contains(name) {
|
||||
true
|
||||
} else {
|
||||
// mro[0] is self, so skip it
|
||||
self.mro.read()[1..]
|
||||
.iter()
|
||||
.find(|cls| cls.attributes.read().contains_key(name))
|
||||
.find(|cls| cls.attributes.contains(name))
|
||||
.is_some_and(|cls| {
|
||||
cls.slots.new.load().map(|f| fn_addr(f))
|
||||
== Some(fn_addr(new_wrapper as NewFunc))
|
||||
@@ -919,17 +919,14 @@ impl PyType {
|
||||
// because the native slot won't call __getattr__.
|
||||
let __getattr__ = identifier!(ctx, __getattr__);
|
||||
let has_getattr = {
|
||||
let attrs = self.attributes.read();
|
||||
let in_self = attrs.contains_key(__getattr__);
|
||||
drop(attrs);
|
||||
// mro[0] is self, so skip it
|
||||
in_self
|
||||
self.attributes.contains(__getattr__)
|
||||
|| self
|
||||
.mro
|
||||
.read()
|
||||
.iter()
|
||||
.skip(1)
|
||||
.any(|cls| cls.attributes.read().contains_key(__getattr__))
|
||||
.any(|cls| cls.attributes.contains(__getattr__))
|
||||
};
|
||||
|
||||
if has_getattr {
|
||||
@@ -1043,23 +1040,15 @@ impl PyType {
|
||||
];
|
||||
|
||||
let has_python_cmp = {
|
||||
// Check self first
|
||||
let attrs = self.attributes.read();
|
||||
let in_self = cmp_names.iter().any(|n| attrs.contains_key(*n));
|
||||
drop(attrs);
|
||||
|
||||
// mro[0] is self, so skip it since we already checked self above
|
||||
in_self
|
||||
// Check self first, then the rest of the MRO
|
||||
cmp_names.iter().any(|n| self.attributes.contains(n))
|
||||
|| self.mro.read()[1..].iter().any(|cls| {
|
||||
let attrs = cls.attributes.read();
|
||||
cmp_names.iter().any(|n| {
|
||||
if let Some(attr) = attrs.get(*n) {
|
||||
cls.attributes.get(n).is_some_and(|attr| {
|
||||
// Check if it's a Python function (not a native descriptor)
|
||||
!attr.class().is(ctx.types.wrapper_descriptor_type)
|
||||
&& !attr.class().is(ctx.types.method_descriptor_type)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
};
|
||||
@@ -1515,10 +1504,9 @@ impl PyType {
|
||||
// SqAssItem is shared by __setitem__ (SeqSetItem) and __delitem__ (SeqDelItem)
|
||||
if ADD {
|
||||
let has_own = {
|
||||
let guard = self.attributes.read();
|
||||
let setitem = ctx.intern_str("__setitem__");
|
||||
let delitem = ctx.intern_str("__delitem__");
|
||||
guard.contains_key(setitem) || guard.contains_key(delitem)
|
||||
self.attributes.contains(setitem) || self.attributes.contains(delitem)
|
||||
};
|
||||
if has_own {
|
||||
self.slots
|
||||
@@ -1568,10 +1556,9 @@ impl PyType {
|
||||
// MpAssSubscript is shared by __setitem__ (MapSetSubscript) and __delitem__ (MapDelSubscript)
|
||||
if ADD {
|
||||
let has_own = {
|
||||
let guard = self.attributes.read();
|
||||
let setitem = ctx.intern_str("__setitem__");
|
||||
let delitem = ctx.intern_str("__delitem__");
|
||||
guard.contains_key(setitem) || guard.contains_key(delitem)
|
||||
self.attributes.contains(setitem) || self.attributes.contains(delitem)
|
||||
};
|
||||
if has_own {
|
||||
self.slots
|
||||
@@ -1694,7 +1681,7 @@ impl PyType {
|
||||
let mro = self.mro.read();
|
||||
|
||||
// Look up in self's dict first
|
||||
let attr_name = self.attributes.read().get(name).cloned();
|
||||
let attr_name = self.attributes.get(name);
|
||||
if let Some(attr) = attr_name {
|
||||
if let Some(func) = try_extract(&attr, &mro) {
|
||||
return SlotLookupResult::NativeSlot(func);
|
||||
@@ -1704,7 +1691,7 @@ impl PyType {
|
||||
|
||||
// Look up in MRO (mro[0] is self, so skip it)
|
||||
for (i, cls) in mro[1..].iter().enumerate() {
|
||||
let attr_name = cls.attributes.read().get(name).cloned();
|
||||
let attr_name = cls.attributes.get(name);
|
||||
if let Some(attr) = attr_name {
|
||||
// Use the slice starting from this class in MRO
|
||||
if let Some(func) = try_extract(&attr, &mro[i + 1..]) {
|
||||
|
||||
@@ -441,11 +441,7 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {
|
||||
// Default __reduce__: only set if not already overridden by the impl's extend_class.
|
||||
// This allows struct sequences like sched_param to provide a custom __reduce__
|
||||
// (equivalent to METH_COEXIST in structseq.c).
|
||||
if !class
|
||||
.attributes
|
||||
.read()
|
||||
.contains_key(ctx.intern_str("__reduce__"))
|
||||
{
|
||||
if !class.attributes.contains(ctx.intern_str("__reduce__")) {
|
||||
class.set_attr(
|
||||
ctx.intern_str("__reduce__"),
|
||||
DEFAULT_STRUCTSEQ_REDUCE.to_proper_method(class, ctx),
|
||||
|
||||
@@ -672,11 +672,7 @@ impl Context {
|
||||
}
|
||||
|
||||
/// Creates a new [`PyGetSet`] with a heap type.
|
||||
///
|
||||
/// # Safety
|
||||
/// In practice, this constructor is safe because a getset is always owned by its `class` type.
|
||||
/// However, it can be broken if used unconventionally.
|
||||
pub unsafe fn new_getset<G, S, T, U>(
|
||||
pub fn new_getset<G, S, T, U>(
|
||||
&self,
|
||||
name: &str,
|
||||
class: &Py<PyType>,
|
||||
@@ -687,8 +683,8 @@ impl Context {
|
||||
G: IntoPyGetterFunc<T>,
|
||||
S: IntoPySetterFunc<U>,
|
||||
{
|
||||
let class = unsafe { &*(class as *const _) };
|
||||
self.new_static_getset(name, class, g, s)
|
||||
let getset = PyGetSet::new(name, class).with_get(g).with_set(s);
|
||||
PyRef::new_ref(getset, self.types.getset_type.to_owned(), None)
|
||||
}
|
||||
|
||||
pub fn new_base_object(&self, class: PyTypeRef, dict: Option<PyDictRef>) -> PyObjectRef {
|
||||
|
||||
Reference in new issue
Block a user