Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions crates/vm/src/builtins/dict.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{
IterStatus, PositionIterInternal, PyBaseExceptionRef, PyGenericAlias, PyMappingProxy, PySet,
PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef, set::PySetInner,
PyStrRef, PyTupleRef, PyType, PyTypeRef, set::PySetInner,
};
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult,
Expand Down Expand Up @@ -568,14 +568,37 @@ impl Py<PyDict> {
}
}

/// Take a python dictionary and convert it to attributes.
pub fn to_attributes(&self, vm: &VirtualMachine) -> PyAttributes {
pub fn to_attributes_with_nonstring(
&self,
vm: &VirtualMachine,
) -> PyResult<(PyAttributes, Option<PyDictRef>)> {
let mut attrs = PyAttributes::default();
let mut non_string: Option<PyDictRef> = None;
for (key, value) in self {
let key: PyRefExact<PyStr> = key.downcast_exact(vm).expect("dict has non-string keys");
attrs.insert(vm.ctx.intern_str(key), value);
if let Some(key) = key.as_interned_str(vm) {
attrs.insert(key, value);
} else {
let dict = non_string.get_or_insert_with(|| vm.ctx.new_dict());
dict.set_item(key.as_object(), value, vm)?;
}
}
Ok((attrs, non_string))
}

/// Take a python dictionary and convert it to attributes.
pub fn to_attributes(&self, vm: &VirtualMachine) -> PyResult<PyAttributes> {
let (attrs, non_string) = self.to_attributes_with_nonstring(vm)?;
if let Some(non_string) = non_string {
let (key, _) = non_string
.into_iter()
.next()
.expect("internal error: non_string dict should not be empty when present");
return Err(vm.new_type_error(format!(
"attributes must be strings, not '{}'",
key.class().name()
)));
}
attrs
Ok(attrs)
}

pub fn get_item_opt<K: DictKey + ?Sized>(
Expand Down
40 changes: 27 additions & 13 deletions crates/vm/src/builtins/mappingproxy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{PyDict, PyDictRef, PyGenericAlias, PyList, PyTuple, PyType, PyTypeRef};
use super::{PyDictRef, PyGenericAlias, PyList, PyTuple, PyType, PyTypeRef};
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
atomic_func,
Expand Down Expand Up @@ -91,13 +91,25 @@ impl Constructor for PyMappingProxy {
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_inner(class, &key, vm),
MappingProxyInner::Mapping(mapping) => mapping.mapping().subscript(&*key, vm).map(Some),
}
}

fn class_get_inner(
class: &PyTypeRef,
key: &PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<Option<PyObjectRef>> {
if let Some(key) = key.as_interned_str(vm) {
return Ok(class.attributes.read().get(key).cloned());
}
if let Some(extra) = class.nonstring_attributes.read().as_ref() {
return extra.get_item_opt(key.as_object(), vm);
}
Ok(None)
}

#[pymethod]
fn get(
&self,
Expand All @@ -121,9 +133,15 @@ 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) => {
if let Some(key) = key.as_interned_str(vm) {
Ok(class.attributes.read().contains_key(key))
} else if let Some(extra) = class.nonstring_attributes.read().as_ref() {
extra.get_item_opt(key, vm).map(|opt| opt.is_some())
} else {
Ok(false)
}
}
MappingProxyInner::Mapping(mapping) => mapping.to_sequence().contains(key, vm),
}
}
Expand All @@ -136,9 +154,7 @@ 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) => c.as_object_dict(vm)?.to_pyobject(vm),
})
}

Expand All @@ -164,9 +180,7 @@ impl PyMappingProxy {
pub fn copy(&self, vm: &VirtualMachine) -> PyResult {
match &self.mapping {
MappingProxyInner::Mapping(d) => vm.call_method(d, identifier!(vm, copy).as_str(), ()),
MappingProxyInner::Class(c) => {
Ok(PyDict::from_attributes(c.attributes.read().clone(), vm)?.to_pyobject(vm))
}
MappingProxyInner::Class(c) => Ok(c.as_object_dict(vm)?.to_pyobject(vm)),
}
}

Expand Down
24 changes: 22 additions & 2 deletions crates/vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{
PyClassMethod, PyDictRef, PyList, PyStr, PyStrInterned, PyStrRef, PyTupleRef, PyWeak,
PyClassMethod, PyDict, PyDictRef, PyList, PyStr, PyStrInterned, PyStrRef, PyTupleRef, PyWeak,
mappingproxy::PyMappingProxy, object, union_,
};
use crate::{
Expand Down Expand Up @@ -41,6 +41,7 @@ pub struct PyType {
pub mro: PyRwLock<Vec<PyTypeRef>>,
pub subclasses: PyRwLock<Vec<PyRef<PyWeak>>>,
pub attributes: PyRwLock<PyAttributes>,
pub nonstring_attributes: PyRwLock<Option<PyDictRef>>,
pub slots: PyTypeSlots,
pub heaptype_ext: Option<Pin<Box<HeapTypeExt>>>,
}
Expand All @@ -56,6 +57,9 @@ unsafe impl crate::object::Traverse for PyType {
.iter()
.map(|(_, v)| v.traverse(tracer_fn))
.count();
if let Some(dict) = self.nonstring_attributes.read_recursive().as_ref() {
dict.traverse(tracer_fn);
}
}
}

Expand Down Expand Up @@ -347,6 +351,7 @@ impl PyType {
mro: PyRwLock::new(mro),
subclasses: PyRwLock::default(),
attributes: PyRwLock::new(attrs),
nonstring_attributes: PyRwLock::default(),
slots,
heaptype_ext: Some(Pin::new(Box::new(heaptype_ext))),
},
Expand Down Expand Up @@ -397,6 +402,7 @@ impl PyType {
mro: PyRwLock::new(mro),
subclasses: PyRwLock::default(),
attributes: PyRwLock::new(attrs),
nonstring_attributes: PyRwLock::default(),
slots,
heaptype_ext: None,
},
Expand Down Expand Up @@ -544,6 +550,16 @@ impl PyType {
attributes
}

pub(crate) fn as_object_dict(&self, vm: &VirtualMachine) -> PyResult<PyDictRef> {
let dict = PyDict::from_attributes(self.attributes.read().clone(), vm)?.into_ref(&vm.ctx);
if let Some(extra) = self.nonstring_attributes.read().clone() {
for (key, value) in extra {
dict.set_item(key.as_object(), value, vm)?;
}
}
Ok(dict)
}

// bound method for every type
pub(crate) fn __new__(zelf: PyRef<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
let (subtype, args): (PyRef<Self>, FuncArgs) = args.bind(vm)?;
Expand Down Expand Up @@ -1128,7 +1144,7 @@ impl Constructor for PyType {
// If __qualname__ is not provided, we can use the name as default
name.clone()
});
let mut attributes = dict.to_attributes(vm);
let (mut attributes, nonstring_attributes) = dict.to_attributes_with_nonstring(vm)?;

if let Some(f) = attributes.get_mut(identifier!(vm, __init_subclass__))
&& f.class().is(vm.ctx.types.function_type)
Expand Down Expand Up @@ -1246,6 +1262,10 @@ impl Constructor for PyType {
)
.map_err(|e| vm.new_type_error(e))?;

if let Some(extra) = nonstring_attributes {
*typ.nonstring_attributes.write() = Some(extra);
}

if let Some(ref slots) = heaptype_slots {
let mut offset = base_member_count;
let class_name = typ.name().to_string();
Expand Down
3 changes: 3 additions & 0 deletions crates/vm/src/object/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
mro: PyRwLock::default(),
subclasses: PyRwLock::default(),
attributes: PyRwLock::new(Default::default()),
nonstring_attributes: PyRwLock::default(),
slots: PyType::make_slots(),
heaptype_ext: None,
};
Expand All @@ -1267,6 +1268,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
mro: PyRwLock::default(),
subclasses: PyRwLock::default(),
attributes: PyRwLock::new(Default::default()),
nonstring_attributes: PyRwLock::default(),
slots: object::PyBaseObject::make_slots(),
heaptype_ext: None,
};
Expand Down Expand Up @@ -1324,6 +1326,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
mro: PyRwLock::new(vec![object_type.clone()]),
subclasses: PyRwLock::default(),
attributes: PyRwLock::default(),
nonstring_attributes: PyRwLock::default(),
slots: PyWeak::make_slots(),
heaptype_ext: None,
};
Expand Down
7 changes: 5 additions & 2 deletions crates/vm/src/stdlib/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,13 @@ pub(crate) mod _thread {
kwargs: OptionalArg<PyDictRef>,
vm: &VirtualMachine,
) -> PyResult<u64> {
let kw_attrs = kwargs
.map(|k| k.to_attributes(vm))
.transpose()?
.unwrap_or_default();
let args = FuncArgs::new(
args.to_vec(),
kwargs
.map_or_else(Default::default, |k| k.to_attributes(vm))
kw_attrs
.into_iter()
.map(|(k, v)| (k.as_str().to_owned(), v))
.collect::<KwArgs>(),
Expand Down
8 changes: 8 additions & 0 deletions extra_tests/snippets/builtin_mappingproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,11 @@ def b():
assert A.__dict__.get("not here", "default") == "default"
assert A.__dict__.get("a", "default") is A.a
assert A.__dict__.get("not here") is None


class B(object):
locals()[42] = "abc"


assert B.__dict__[42] == "abc"
assert 42 in B.__dict__