diff --git a/vm/src/builtins/builtinfunc.rs b/vm/src/builtins/builtinfunc.rs index 9092b8787..e7dd5d2d5 100644 --- a/vm/src/builtins/builtinfunc.rs +++ b/vm/src/builtins/builtinfunc.rs @@ -137,7 +137,7 @@ impl PyValue for PyBuiltinMethod { impl fmt::Debug for PyBuiltinMethod { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "method descriptor") + write!(f, "method descriptor for '{}'", self.value.name) } } @@ -172,7 +172,7 @@ impl Callable for PyBuiltinMethod { } } -#[pyimpl(with(SlotDescriptor, Callable))] +#[pyimpl(with(SlotDescriptor, Callable), flags(METHOD_DESCR))] impl PyBuiltinMethod { #[pyproperty(magic)] fn name(&self) -> PyStrRef { diff --git a/vm/src/builtins/function.rs b/vm/src/builtins/function.rs index 2c2a7aaed..914d494fd 100644 --- a/vm/src/builtins/function.rs +++ b/vm/src/builtins/function.rs @@ -302,7 +302,7 @@ impl PyValue for PyFunction { } } -#[pyimpl(with(SlotDescriptor, Callable), flags(HAS_DICT))] +#[pyimpl(with(SlotDescriptor, Callable), flags(HAS_DICT, METHOD_DESCR))] impl PyFunction { #[pyproperty(magic)] fn code(&self) -> PyCodeRef { diff --git a/vm/src/builtins/object.rs b/vm/src/builtins/object.rs index 213749139..09722d6a6 100644 --- a/vm/src/builtins/object.rs +++ b/vm/src/builtins/object.rs @@ -240,7 +240,7 @@ impl PyBaseObject { #[pymethod(name = "__getattribute__")] #[pyslot] - fn getattro(obj: PyObjectRef, name: PyStrRef, vm: &VirtualMachine) -> PyResult { + pub(crate) fn getattro(obj: PyObjectRef, name: PyStrRef, vm: &VirtualMachine) -> PyResult { vm_trace!("object.__getattribute__({:?}, {:?})", obj, name); vm.generic_getattribute(obj, name) } diff --git a/vm/src/function.rs b/vm/src/function.rs index 42626593f..bfef80c7c 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -13,8 +13,13 @@ use result_like::impl_option_like; use std::marker::PhantomData; use std::ops::RangeInclusive; -pub trait IntoFuncArgs { +pub trait IntoFuncArgs: Sized { fn into_args(self, vm: &VirtualMachine) -> FuncArgs; + fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs { + let mut args = self.into_args(vm); + args.prepend_arg(obj); + args + } } impl IntoFuncArgs for T @@ -26,11 +31,6 @@ where } } -macro_rules! count { - () => (0usize); - ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*)); -} - // A tuple of values that each implement `IntoPyObject` represents a sequence of // arguments that can be bound and passed to a built-in function. macro_rules! into_func_args_from_tuple { @@ -42,9 +42,13 @@ macro_rules! into_func_args_from_tuple { #[inline] fn into_args(self, vm: &VirtualMachine) -> FuncArgs { let ($($n,)*) = self; - let mut result = Vec::with_capacity(count!($($n,)*) + 1); - $(result.push($n.into_pyobject(vm), );)* - result.into() + vec![$($n.into_pyobject(vm),)*].into() + } + + #[inline] + fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs { + let ($($n,)*) = self; + vec![obj, $($n.into_pyobject(vm),)*].into() } } }; diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index e8f688b93..8df2d812c 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1251,3 +1251,97 @@ pub fn hash_iter_unordered<'a, I: IntoIterator>( ) -> PyResult { rustpython_common::hash::hash_iter_unordered(iter, |obj| vm._hash(obj)) } + +#[derive(Debug)] +pub enum PyMethod { + Function { + target: PyObjectRef, + func: PyObjectRef, + }, + Attribute(PyObjectRef), +} + +impl PyMethod { + pub fn get(obj: PyObjectRef, name: pystr::PyStrRef, vm: &VirtualMachine) -> PyResult { + let cls = obj.class(); + let getattro = cls.mro_find_map(|cls| cls.slots.getattro.load()).unwrap(); + if getattro as usize != object::PyBaseObject::getattro as usize { + drop(cls); + return getattro(obj, name, vm).map(Self::Attribute); + } + + let mut is_method = false; + + let cls_attr = match cls.get_attr(name.borrow_value()) { + Some(descr) => { + let descr_cls = descr.class(); + let descr_get = if descr_cls.slots.flags.has_feature(PyTpFlags::METHOD_DESCR) { + is_method = true; + None + } else { + let descr_get = descr_cls.mro_find_map(|cls| cls.slots.descr_get.load()); + if let Some(descr_get) = descr_get { + if descr_cls.has_attr("__set__") { + drop(descr_cls); + let cls = PyLease::into_pyref(cls).into_object(); + return descr_get(descr, Some(obj), Some(cls), vm).map(Self::Attribute); + } + } + descr_get + }; + drop(descr_cls); + Some((descr, descr_get)) + } + None => None, + }; + + if let Some(dict) = obj.dict() { + if let Some(attr) = dict.get_item_option(name.clone(), vm)? { + return Ok(Self::Attribute(attr)); + } + } + + if let Some((attr, descr_get)) = cls_attr { + match descr_get { + None if is_method => { + drop(cls); + Ok(Self::Function { + target: obj, + func: attr, + }) + } + Some(descr_get) => { + let cls = PyLease::into_pyref(cls).into_object(); + descr_get(attr, Some(obj), Some(cls), vm).map(Self::Attribute) + } + None => Ok(Self::Attribute(attr)), + } + } else if let Some(getter) = cls.get_attr("__getattr__") { + drop(cls); + vm.invoke(&getter, (obj, name)).map(Self::Attribute) + } else { + Err(vm.new_attribute_error(format!( + "'{}' object has not attribute '{}'", + cls.name, name + ))) + } + } + + pub fn invoke(self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult { + let (func, args) = match self { + PyMethod::Function { target, func } => (func, args.into_method_args(target, vm)), + PyMethod::Attribute(func) => (func, args.into_args(vm)), + }; + vm.invoke(&func, args) + } + + pub fn invoke_ref(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult { + let (func, args) = match self { + PyMethod::Function { target, func } => { + (func, args.into_method_args(target.clone(), vm)) + } + PyMethod::Attribute(func) => (func, args.into_args(vm)), + }; + vm.invoke(func, args) + } +} diff --git a/vm/src/slots.rs b/vm/src/slots.rs index 833f271d9..1d9dadfe9 100644 --- a/vm/src/slots.rs +++ b/vm/src/slots.rs @@ -15,6 +15,7 @@ bitflags! { pub struct PyTpFlags: u64 { const HEAPTYPE = 1 << 9; const BASETYPE = 1 << 10; + const METHOD_DESCR = 1 << 17; const HAS_DICT = 1 << 40; #[cfg(debug_assertions)] diff --git a/vm/src/vm.rs b/vm/src/vm.rs index b62d0950a..e5594011b 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -29,8 +29,8 @@ use crate::frame::{ExecutionResult, Frame, FrameRef}; use crate::function::{FuncArgs, IntoFuncArgs}; use crate::pyobject::{ BorrowValue, Either, IdProtocol, IntoPyObject, ItemProtocol, PyArithmaticValue, PyContext, - PyObject, PyObjectRef, PyRef, PyRefExact, PyResult, PyValue, TryFromObject, TryIntoRef, - TypeProtocol, + PyLease, PyMethod, PyObject, PyObjectRef, PyRef, PyRefExact, PyResult, PyValue, TryFromObject, + TryIntoRef, TypeProtocol, }; use crate::scope::Scope; use crate::slots::PyComparisonOp; @@ -965,21 +965,15 @@ impl VirtualMachine { self.call_get_descriptor(attr, obj).unwrap_or_else(Ok) } + #[inline] pub fn call_method(&self, obj: &PyObjectRef, method_name: &str, args: T) -> PyResult where T: IntoFuncArgs, { flame_guard!(format!("call_method({:?})", method_name)); - // This is only used in the vm for magic methods, which use a greatly simplified attribute lookup. - match obj.get_class_attr(method_name) { - Some(func) => { - vm_trace!("vm.call_method {:?} {:?} -> {:?}", obj, method_name, func); - let wrapped = self.call_if_get_descriptor(func, obj.clone())?; - self.invoke(&wrapped, args) - } - None => Err(self.new_type_error(format!("Unsupported method: {}", method_name))), - } + PyMethod::get(obj.clone(), PyStr::from(method_name).into_ref(self), self)? + .invoke(args, self) } fn _invoke(&self, callable: &PyObjectRef, args: FuncArgs) -> PyResult {