/*! Python `attribute` descriptor class. (PyGetSet) */ use super::objtype::PyClassRef; use crate::function::{OptionalArg, OwnedParam, RefParam}; use crate::pyobject::{ IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::slots::SlotDescriptor; use crate::vm::VirtualMachine; pub type PyGetterFunc = Box PyResult>; pub type PySetterFunc = Box PyResult<()>>; pub trait IntoPyGetterFunc { fn into_getter(self) -> PyGetterFunc; } impl IntoPyGetterFunc<(OwnedParam, R, VirtualMachine)> for F where F: Fn(T, &VirtualMachine) -> R + 'static, T: TryFromObject, R: IntoPyObject, { fn into_getter(self) -> PyGetterFunc { Box::new(move |vm, obj| { let obj = T::try_from_object(vm, obj)?; (self)(obj, vm).into_pyobject(vm) }) } } impl IntoPyGetterFunc<(RefParam, R, VirtualMachine)> for F where F: Fn(&S, &VirtualMachine) -> R + 'static, S: PyValue, R: IntoPyObject, { fn into_getter(self) -> PyGetterFunc { Box::new(move |vm, obj| { let zelf = PyRef::::try_from_object(vm, obj)?; (self)(&zelf, vm).into_pyobject(vm) }) } } impl IntoPyGetterFunc<(OwnedParam, R)> for F where F: Fn(T) -> R + 'static, T: TryFromObject, R: IntoPyObject, { fn into_getter(self) -> PyGetterFunc { IntoPyGetterFunc::into_getter(move |obj, _vm: &VirtualMachine| (self)(obj)) } } impl IntoPyGetterFunc<(RefParam, R)> for F where F: Fn(&S) -> R + 'static, S: PyValue, R: IntoPyObject, { fn into_getter(self) -> PyGetterFunc { IntoPyGetterFunc::into_getter(move |zelf: &S, _vm: &VirtualMachine| (self)(zelf)) } } pub trait IntoPyNoResult { fn into_noresult(self) -> PyResult<()>; } impl IntoPyNoResult for () { fn into_noresult(self) -> PyResult<()> { Ok(()) } } impl IntoPyNoResult for PyResult<()> { fn into_noresult(self) -> PyResult<()> { self } } pub trait IntoPySetterFunc { fn into_setter(self) -> PySetterFunc; } impl IntoPySetterFunc<(OwnedParam, V, R, VirtualMachine)> for F where F: Fn(T, V, &VirtualMachine) -> R + 'static, T: TryFromObject, V: TryFromObject, R: IntoPyNoResult, { fn into_setter(self) -> PySetterFunc { Box::new(move |vm, obj, value| { let obj = T::try_from_object(vm, obj)?; let value = V::try_from_object(vm, value)?; (self)(obj, value, vm).into_noresult() }) } } impl IntoPySetterFunc<(RefParam, V, R, VirtualMachine)> for F where F: Fn(&S, V, &VirtualMachine) -> R + 'static, S: PyValue, V: TryFromObject, R: IntoPyNoResult, { fn into_setter(self) -> PySetterFunc { Box::new(move |vm, obj, value| { let zelf = PyRef::::try_from_object(vm, obj)?; let value = V::try_from_object(vm, value)?; (self)(&zelf, value, vm).into_noresult() }) } } impl IntoPySetterFunc<(OwnedParam, V, R)> for F where F: Fn(T, V) -> R + 'static, T: TryFromObject, V: TryFromObject, R: IntoPyNoResult, { fn into_setter(self) -> PySetterFunc { IntoPySetterFunc::into_setter(move |obj, v, _vm: &VirtualMachine| (self)(obj, v)) } } impl IntoPySetterFunc<(RefParam, V, R)> for F where F: Fn(&S, V) -> R + 'static, S: PyValue, V: TryFromObject, R: IntoPyNoResult, { fn into_setter(self) -> PySetterFunc { IntoPySetterFunc::into_setter(move |zelf: &S, v, _vm: &VirtualMachine| (self)(zelf, v)) } } #[pyclass] pub struct PyGetSet { name: String, getter: Option, setter: Option, // doc: Option, } impl std::fmt::Debug for PyGetSet { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "PyGetSet {{ name: {}, getter: {}, setter: {} }}", self.name, if self.getter.is_some() { "Some" } else { "None" }, if self.setter.is_some() { "Some" } else { "None" }, ) } } impl PyValue for PyGetSet { fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.getset_type() } } pub type PyGetSetRef = PyRef; impl SlotDescriptor for PyGetSet { fn descr_get( vm: &VirtualMachine, zelf: PyObjectRef, obj: Option, _cls: OptionalArg, ) -> PyResult { let (zelf, obj) = match Self::_check(zelf, obj, vm) { Ok(obj) => obj, Err(result) => return result, }; if let Some(ref f) = zelf.getter { f(vm, obj) } else { Err(vm.new_attribute_error(format!( "attribute '{}' of '{}' objects is not readable", zelf.name, Self::class(vm).name ))) } } } impl PyGetSet { pub fn with_get(name: String, getter: G) -> Self where G: IntoPyGetterFunc, { Self { name, getter: Some(getter.into_getter()), setter: None, } } pub fn with_get_set(name: String, getter: G, setter: S) -> Self where G: IntoPyGetterFunc, S: IntoPySetterFunc, { Self { name, getter: Some(getter.into_getter()), setter: Some(setter.into_setter()), } } } #[pyimpl(with(SlotDescriptor))] impl PyGetSet { // Descriptor methods #[pymethod(magic)] fn set(&self, obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { if let Some(ref f) = self.setter { f(vm, obj, value) } else { Err(vm.new_attribute_error(format!( "attribute '{}' of '{}' objects is not writable", self.name, obj.class().name ))) } } // TODO: give getset_descriptors names #[pyproperty(magic)] fn name(&self) {} } pub(crate) fn init(context: &PyContext) { PyGetSet::extend_class(context, &context.types.getset_type); }