From aa2a7f01ee7c9ed4cac8731c788c3013653dc5aa Mon Sep 17 00:00:00 2001 From: snowapril Date: Thu, 30 Sep 2021 22:48:58 +0900 Subject: [PATCH] add PyMapping as PyObjectRef wrapper Modify original `PyMapping` to `PyMappingMethods` and create `PyMapping` struct as `PyObjectRef` wrapper for implementing `PyMapping_*` functions. https://docs.python.org/3/c-api/mapping.html Signed-off-by: snowapril --- stdlib/src/array.rs | 8 +- vm/src/builtins/bytearray.rs | 8 +- vm/src/builtins/bytes.rs | 6 +- vm/src/builtins/dict.rs | 8 +- vm/src/builtins/list.rs | 6 +- vm/src/builtins/mappingproxy.rs | 8 +- vm/src/builtins/memory.rs | 6 +- vm/src/builtins/pytype.rs | 4 +- vm/src/builtins/range.rs | 6 +- vm/src/builtins/tuple.rs | 6 +- vm/src/protocol/mapping.rs | 141 +++++++++++++++++++++++++++----- vm/src/protocol/mod.rs | 2 +- vm/src/pyobject.rs | 12 +-- vm/src/slots.rs | 8 +- 14 files changed, 166 insertions(+), 63 deletions(-) diff --git a/stdlib/src/array.rs b/stdlib/src/array.rs index abf4065d9..77273bda9 100644 --- a/stdlib/src/array.rs +++ b/stdlib/src/array.rs @@ -17,7 +17,9 @@ mod array { }, class_or_notimplemented, function::{ArgBytesLike, ArgIterable, OptionalArg}, - protocol::{BufferInternal, BufferOptions, PyBuffer, PyIterReturn, PyMapping, ResizeGuard}, + protocol::{ + BufferInternal, BufferOptions, PyBuffer, PyIterReturn, PyMappingMethods, ResizeGuard, + }, sliceable::{saturate_index, PySliceableSequence, PySliceableSequenceMut, SequenceIndex}, slots::{ AsBuffer, AsMapping, Comparable, Iterable, IteratorIterable, PyComparisonOp, @@ -1165,8 +1167,8 @@ mod array { } impl AsMapping for PyArray { - fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { - Ok(PyMapping { + fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { + Ok(PyMappingMethods { length: Some(Self::length), subscript: Some(Self::subscript), ass_subscript: Some(Self::ass_subscript), diff --git a/vm/src/builtins/bytearray.rs b/vm/src/builtins/bytearray.rs index 26557401b..2d8e4c849 100644 --- a/vm/src/builtins/bytearray.rs +++ b/vm/src/builtins/bytearray.rs @@ -15,7 +15,9 @@ use crate::{ ByteInnerTranslateOptions, DecodeArgs, PyBytesInner, }, function::{ArgBytesLike, ArgIterable, FuncArgs, OptionalArg, OptionalOption}, - protocol::{BufferInternal, BufferOptions, PyBuffer, PyIterReturn, PyMapping, ResizeGuard}, + protocol::{ + BufferInternal, BufferOptions, PyBuffer, PyIterReturn, PyMappingMethods, ResizeGuard, + }, sliceable::{PySliceableSequence, PySliceableSequenceMut, SequenceIndex}, slots::{ AsBuffer, AsMapping, Callable, Comparable, Hashable, Iterable, IteratorIterable, @@ -716,8 +718,8 @@ impl<'a> ResizeGuard<'a> for PyByteArray { } impl AsMapping for PyByteArray { - fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { - Ok(PyMapping { + fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { + Ok(PyMappingMethods { length: Some(Self::length), subscript: Some(Self::subscript), ass_subscript: Some(Self::ass_subscript), diff --git a/vm/src/builtins/bytes.rs b/vm/src/builtins/bytes.rs index 18ce64929..f6ab7d478 100644 --- a/vm/src/builtins/bytes.rs +++ b/vm/src/builtins/bytes.rs @@ -7,7 +7,7 @@ use crate::{ }, common::hash::PyHash, function::{ArgBytesLike, ArgIterable, OptionalArg, OptionalOption}, - protocol::{BufferInternal, BufferOptions, PyBuffer, PyIterReturn, PyMapping}, + protocol::{BufferInternal, BufferOptions, PyBuffer, PyIterReturn, PyMappingMethods}, slots::{ AsBuffer, AsMapping, Callable, Comparable, Hashable, Iterable, IteratorIterable, PyComparisonOp, SlotConstructor, SlotIterator, @@ -541,8 +541,8 @@ impl BufferInternal for PyRef { } impl AsMapping for PyBytes { - fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { - Ok(PyMapping { + fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { + Ok(PyMappingMethods { length: Some(Self::length), subscript: Some(Self::subscript), ass_subscript: None, diff --git a/vm/src/builtins/dict.rs b/vm/src/builtins/dict.rs index f856567be..122831d0b 100644 --- a/vm/src/builtins/dict.rs +++ b/vm/src/builtins/dict.rs @@ -4,7 +4,7 @@ use crate::{ common::ascii, dictdatatype::{self, DictKey}, function::{ArgIterable, FuncArgs, KwArgs, OptionalArg}, - protocol::{PyIterReturn, PyMapping}, + protocol::{PyIterReturn, PyMappingMethods}, slots::{ AsMapping, Comparable, Hashable, Iterable, IteratorIterable, PyComparisonOp, SlotIterator, Unhashable, @@ -412,8 +412,8 @@ impl PyDict { } impl AsMapping for PyDict { - fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { - Ok(PyMapping { + fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { + Ok(PyMappingMethods { length: Some(Self::length), subscript: Some(Self::subscript), ass_subscript: Some(Self::ass_subscript), @@ -685,7 +685,7 @@ macro_rules! dict_iterator { } impl $name { - fn new(dict: PyDictRef) -> Self { + pub fn new(dict: PyDictRef) -> Self { $name { dict } } } diff --git a/vm/src/builtins/list.rs b/vm/src/builtins/list.rs index 34dd2c2bf..bee377e7a 100644 --- a/vm/src/builtins/list.rs +++ b/vm/src/builtins/list.rs @@ -8,7 +8,7 @@ use crate::common::lock::{ }; use crate::{ function::{ArgIterable, FuncArgs, OptionalArg}, - protocol::{PyIterReturn, PyMapping}, + protocol::{PyIterReturn, PyMappingMethods}, sequence::{self, SimpleSeq}, sliceable::{PySliceableSequence, PySliceableSequenceMut, SequenceIndex}, slots::{ @@ -418,8 +418,8 @@ impl PyList { } impl AsMapping for PyList { - fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { - Ok(PyMapping { + fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { + Ok(PyMappingMethods { length: Some(Self::length), subscript: Some(Self::subscript), ass_subscript: Some(Self::ass_subscript), diff --git a/vm/src/builtins/mappingproxy.rs b/vm/src/builtins/mappingproxy.rs index 035e5b376..a5fbaad98 100644 --- a/vm/src/builtins/mappingproxy.rs +++ b/vm/src/builtins/mappingproxy.rs @@ -1,7 +1,7 @@ use super::{PyDict, PyList, PyStrRef, PyTuple, PyTypeRef}; use crate::{ function::OptionalArg, - protocol::PyMapping, + protocol::{PyMapping, PyMappingMethods}, slots::{AsMapping, Iterable, SlotConstructor}, IntoPyObject, ItemProtocol, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, VirtualMachine, @@ -37,7 +37,7 @@ impl SlotConstructor for PyMappingProxy { type Args = PyObjectRef; fn py_new(cls: PyTypeRef, mapping: Self::Args, vm: &VirtualMachine) -> PyResult { - if !PyMapping::check(&mapping, vm) + if !PyMapping::check(&mapping) || mapping.payload_if_subclass::(vm).is_some() || mapping.payload_if_subclass::(vm).is_some() { @@ -140,8 +140,8 @@ impl PyMappingProxy { } impl AsMapping for PyMappingProxy { - fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { - Ok(PyMapping { + fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { + Ok(PyMappingMethods { length: None, subscript: Some(Self::subscript), ass_subscript: None, diff --git a/vm/src/builtins/memory.rs b/vm/src/builtins/memory.rs index fa8a2f98b..1dd313dd4 100644 --- a/vm/src/builtins/memory.rs +++ b/vm/src/builtins/memory.rs @@ -8,7 +8,7 @@ use crate::common::{ use crate::{ bytesinner::bytes_to_hex, function::{FuncArgs, OptionalArg}, - protocol::{BufferInternal, BufferOptions, PyBuffer, PyMapping}, + protocol::{BufferInternal, BufferOptions, PyBuffer, PyMappingMethods}, sliceable::{convert_slice, wrap_index, SequenceIndex}, slots::{AsBuffer, AsMapping, Comparable, Hashable, PyComparisonOp, SlotConstructor}, stdlib::pystruct::FormatSpec, @@ -735,8 +735,8 @@ impl BufferInternal for PyRef { } impl AsMapping for PyMemoryView { - fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { - Ok(PyMapping { + fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { + Ok(PyMappingMethods { length: Some(Self::length), subscript: Some(Self::subscript), ass_subscript: Some(Self::ass_subscript), diff --git a/vm/src/builtins/pytype.rs b/vm/src/builtins/pytype.rs index bd8757cef..9db1530c8 100644 --- a/vm/src/builtins/pytype.rs +++ b/vm/src/builtins/pytype.rs @@ -5,7 +5,7 @@ use super::{ use crate::common::{ascii, lock::PyRwLock}; use crate::{ function::{FuncArgs, KwArgs, OptionalArg}, - protocol::{PyIterReturn, PyMapping}, + protocol::{PyIterReturn, PyMappingMethods}, slots::{self, Callable, PyTypeFlags, PyTypeSlots, SlotGetattro, SlotSetattro}, utils::Either, IdProtocol, PyAttributes, PyClassImpl, PyContext, PyLease, PyObjectRef, PyRef, PyResult, @@ -283,7 +283,7 @@ impl PyType { } let func: slots::MappingFunc = |zelf, _vm| { - Ok(PyMapping { + Ok(PyMappingMethods { length: then_some_closure!(zelf.has_class_attr("__len__"), |zelf, vm| { vm.call_special_method(zelf, "__len__", ()).map(|obj| { obj.payload_if_subclass::(vm) diff --git a/vm/src/builtins/range.rs b/vm/src/builtins/range.rs index 792b29a2a..6c9d0a0db 100644 --- a/vm/src/builtins/range.rs +++ b/vm/src/builtins/range.rs @@ -2,7 +2,7 @@ use super::{PyInt, PyIntRef, PySlice, PySliceRef, PyTypeRef}; use crate::common::hash::PyHash; use crate::{ function::{FuncArgs, OptionalArg}, - protocol::{PyIterReturn, PyMapping}, + protocol::{PyIterReturn, PyMappingMethods}, slots::{ AsMapping, Comparable, Hashable, Iterable, IteratorIterable, PyComparisonOp, SlotIterator, }, @@ -375,8 +375,8 @@ impl PyRange { } impl AsMapping for PyRange { - fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { - Ok(PyMapping { + fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { + Ok(PyMappingMethods { length: Some(Self::length), subscript: Some(Self::subscript), ass_subscript: None, diff --git a/vm/src/builtins/tuple.rs b/vm/src/builtins/tuple.rs index 6e7199e70..3f5e8e51d 100644 --- a/vm/src/builtins/tuple.rs +++ b/vm/src/builtins/tuple.rs @@ -6,7 +6,7 @@ use super::{ use crate::common::hash::PyHash; use crate::{ function::OptionalArg, - protocol::{PyIterReturn, PyMapping}, + protocol::{PyIterReturn, PyMappingMethods}, sequence::{self, SimpleSeq}, sliceable::PySliceableSequence, slots::{ @@ -288,8 +288,8 @@ impl PyTuple { } impl AsMapping for PyTuple { - fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { - Ok(PyMapping { + fn as_mapping(_zelf: &PyRef, _vm: &VirtualMachine) -> PyResult { + Ok(PyMappingMethods { length: Some(Self::length), subscript: Some(Self::subscript), ass_subscript: None, diff --git a/vm/src/protocol/mapping.rs b/vm/src/protocol/mapping.rs index 3003edcc4..bf832cf1f 100644 --- a/vm/src/protocol/mapping.rs +++ b/vm/src/protocol/mapping.rs @@ -1,36 +1,135 @@ -//! Mapping protocol - -use crate::{vm::VirtualMachine, PyObjectRef, PyResult, TryFromBorrowedObject, TypeProtocol}; +use crate::{ + builtins::dict::{PyDictKeys, PyDictRef, PyDictValues}, + builtins::list::PyList, + vm::VirtualMachine, + IdProtocol, IntoPyObject, PyObjectRef, PyResult, TryFromObject, TypeProtocol, +}; +use std::borrow::Borrow; +use std::ops::Deref; +// Mapping protocol +// https://docs.python.org/3/c-api/mapping.html #[allow(clippy::type_complexity)] -pub struct PyMapping { +#[derive(Default)] +pub struct PyMappingMethods { pub length: Option PyResult>, pub subscript: Option PyResult>, pub ass_subscript: Option, &VirtualMachine) -> PyResult<()>>, } -impl PyMapping { - pub fn check(cls: &PyObjectRef, vm: &VirtualMachine) -> bool { - if let Ok(mapping) = PyMapping::try_from_borrowed_object(vm, cls) { - mapping.subscript.is_some() - } else { - false +#[derive(Debug, Clone)] +#[repr(transparent)] +pub struct PyMapping(T) +where + T: Borrow; + +impl PyMapping { + pub fn into_object(self) -> PyObjectRef { + self.0 + } + + pub fn check(obj: &PyObjectRef) -> bool { + obj.class() + .mro_find_map(|x| x.slots.as_mapping.load()) + .is_some() + } + + pub fn methods(&self, vm: &VirtualMachine) -> PyMappingMethods { + let obj_cls = self.0.class(); + for cls in obj_cls.iter_mro() { + if let Some(f) = cls.slots.as_mapping.load() { + return f(&self.0, vm).unwrap(); + } } + PyMappingMethods::default() } } -impl TryFromBorrowedObject for PyMapping { - fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { - let obj_cls = obj.class(); - for cls in obj_cls.iter_mro() { - if let Some(f) = cls.slots.as_mapping.load() { - return f(obj, vm); - } +impl PyMapping +where + T: Borrow, +{ + pub fn new(obj: T) -> Self { + Self(obj) + } + + pub fn keys(&self, vm: &VirtualMachine) -> PyResult { + if self.0.borrow().is(&vm.ctx.types.dict_type) { + Ok( + PyDictKeys::new(PyDictRef::try_from_object(vm, self.0.borrow().clone())?) + .into_pyobject(vm), + ) + } else { + Self::method_output_as_list(self.0.borrow(), "keys", vm) + } + } + + pub fn values(&self, vm: &VirtualMachine) -> PyResult { + if self.0.borrow().is(&vm.ctx.types.dict_type) { + Ok( + PyDictValues::new(PyDictRef::try_from_object(vm, self.0.borrow().clone())?) + .into_pyobject(vm), + ) + } else { + Self::method_output_as_list(self.0.borrow(), "values", vm) + } + } + + fn method_output_as_list( + obj: &PyObjectRef, + method_name: &str, + vm: &VirtualMachine, + ) -> PyResult { + let meth_output = vm.call_method(obj, method_name, ())?; + if meth_output.is(&vm.ctx.types.list_type) { + return Ok(meth_output); + } + + let iter = meth_output.clone().get_iter(vm).map_err(|_| { + vm.new_type_error(format!( + "{}.{}() returned a non-iterable (type {})", + obj.class(), + method_name, + meth_output.class() + )) + })?; + + Ok(PyList::from(vm.extract_elements(&iter)?).into_pyobject(vm)) + } +} + +impl Borrow for PyMapping +where + T: Borrow, +{ + fn borrow(&self) -> &PyObjectRef { + self.0.borrow() + } +} + +impl Deref for PyMapping +where + T: Borrow, +{ + type Target = PyObjectRef; + fn deref(&self) -> &Self::Target { + self.0.borrow() + } +} + +impl IntoPyObject for PyMapping { + fn into_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef { + self.into_object() + } +} + +impl TryFromObject for PyMapping { + fn try_from_object(vm: &VirtualMachine, mapping: PyObjectRef) -> PyResult { + if Self::check(&mapping) { + Ok(Self::new(mapping)) + } else { + Err(vm.new_type_error(format!("{} is not a mapping object", mapping.class()))) } - Err(vm.new_type_error(format!( - "a dict-like object is required, not '{}'", - obj_cls.name() - ))) } } diff --git a/vm/src/protocol/mod.rs b/vm/src/protocol/mod.rs index eb4ff328c..548eaaee1 100644 --- a/vm/src/protocol/mod.rs +++ b/vm/src/protocol/mod.rs @@ -4,4 +4,4 @@ mod mapping; pub use buffer::{BufferInternal, BufferOptions, PyBuffer, ResizeGuard}; pub use iter::{PyIter, PyIterReturn}; -pub(crate) use mapping::PyMapping; +pub use mapping::{PyMapping, PyMappingMethods}; diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 5306d28a7..b51e5a000 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -640,8 +640,8 @@ where T: IntoPyObject, { fn get_item(&self, key: T, vm: &VirtualMachine) -> PyResult { - if let Ok(map) = PyMapping::try_from_borrowed_object(vm, self) { - if let Some(getitem) = map.subscript { + if let Ok(mapping) = PyMapping::try_from_object(vm, self.clone()) { + if let Some(getitem) = mapping.methods(vm).subscript { return getitem(self.clone(), key.into_pyobject(vm), vm); } } @@ -663,8 +663,8 @@ where } fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - if let Ok(map) = PyMapping::try_from_borrowed_object(vm, self) { - if let Some(setitem) = map.ass_subscript { + if let Ok(mapping) = PyMapping::try_from_object(vm, self.clone()) { + if let Some(setitem) = mapping.methods(vm).ass_subscript { return setitem(self.clone(), key.into_pyobject(vm), Some(value), vm); } } @@ -681,8 +681,8 @@ where } fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult<()> { - if let Ok(map) = PyMapping::try_from_borrowed_object(vm, self) { - if let Some(setitem) = map.ass_subscript { + if let Ok(mapping) = PyMapping::try_from_object(vm, self.clone()) { + if let Some(setitem) = mapping.methods(vm).ass_subscript { return setitem(self.clone(), key.into_pyobject(vm), None, vm); } } diff --git a/vm/src/slots.rs b/vm/src/slots.rs index 7252cc30f..99c25264e 100644 --- a/vm/src/slots.rs +++ b/vm/src/slots.rs @@ -2,7 +2,7 @@ use crate::builtins::{PyStrRef, PyTypeRef}; use crate::common::hash::PyHash; use crate::common::lock::PyRwLock; use crate::function::{FromArgs, FuncArgs, OptionalArg}; -use crate::protocol::{PyBuffer, PyIterReturn, PyMapping}; +use crate::protocol::{PyBuffer, PyIterReturn, PyMappingMethods}; use crate::utils::Either; use crate::VirtualMachine; use crate::{ @@ -70,7 +70,7 @@ pub(crate) type GetattroFunc = fn(PyObjectRef, PyStrRef, &VirtualMachine) -> PyR pub(crate) type SetattroFunc = fn(&PyObjectRef, PyStrRef, Option, &VirtualMachine) -> PyResult<()>; pub(crate) type BufferFunc = fn(&PyObjectRef, &VirtualMachine) -> PyResult; -pub(crate) type MappingFunc = fn(&PyObjectRef, &VirtualMachine) -> PyResult; +pub(crate) type MappingFunc = fn(&PyObjectRef, &VirtualMachine) -> PyResult; pub(crate) type IterFunc = fn(PyObjectRef, &VirtualMachine) -> PyResult; pub(crate) type IterNextFunc = fn(&PyObjectRef, &VirtualMachine) -> PyResult; @@ -536,7 +536,7 @@ pub trait AsBuffer: PyValue { #[pyimpl] pub trait AsMapping: PyValue { #[pyslot] - fn slot_as_mapping(zelf: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn slot_as_mapping(zelf: &PyObjectRef, vm: &VirtualMachine) -> PyResult { let zelf = zelf .downcast_ref() .ok_or_else(|| vm.new_type_error("unexpected payload for as_mapping".to_owned()))?; @@ -563,7 +563,7 @@ pub trait AsMapping: PyValue { }) } - fn as_mapping(zelf: &PyRef, vm: &VirtualMachine) -> PyResult; + fn as_mapping(zelf: &PyRef, vm: &VirtualMachine) -> PyResult; fn length(zelf: PyObjectRef, _vm: &VirtualMachine) -> PyResult;