From 1fe526bb66db8f469b16324384f2851124e75edc Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 11 Jan 2020 00:21:50 +0900 Subject: [PATCH 1/7] hide member of classmethod --- vm/src/obj/objclassmethod.rs | 10 ++++++++-- vm/src/pyobject.rs | 4 +--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/vm/src/obj/objclassmethod.rs b/vm/src/obj/objclassmethod.rs index c3368da27..c61b0965e 100644 --- a/vm/src/obj/objclassmethod.rs +++ b/vm/src/obj/objclassmethod.rs @@ -29,10 +29,16 @@ use crate::vm::VirtualMachine; #[pyclass] #[derive(Clone, Debug)] pub struct PyClassMethod { - pub callable: PyObjectRef, + callable: PyObjectRef, } pub type PyClassMethodRef = PyRef; +impl PyClassMethod { + pub fn new(value: PyObjectRef) -> Self { + Self { callable: value } + } +} + impl PyValue for PyClassMethod { const HAVE_DICT: bool = true; @@ -73,7 +79,7 @@ impl PyClassMethod { } } -pub fn init(context: &PyContext) { +pub(crate) fn init(context: &PyContext) { PyClassMethod::extend_class(context, &context.types.classmethod_type); extend_class!(context, context.types.classmethod_type, { "__get__" => context.new_method(PyClassMethod::get), diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 3dbd96f70..9f38e4328 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -492,9 +492,7 @@ impl PyContext { F: IntoPyNativeFunc, { PyObject::new( - PyClassMethod { - callable: self.new_method(f), - }, + PyClassMethod::new(self.new_method(f)), self.classmethod_type(), None, ) From 582e5dfca96bfd6f07e8ef5ee6fdc055fb9b6911 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 11 Jan 2020 00:53:05 +0900 Subject: [PATCH 2/7] cleanup tuple --- vm/src/obj/objtuple.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index e39528f20..15d00fa0a 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -7,8 +7,9 @@ use super::objtype::PyClassRef; use crate::function::OptionalArg; use crate::pyhash; use crate::pyobject::{ - IntoPyObject, PyArithmaticValue::*, PyClassImpl, PyComparisonValue, PyContext, PyObjectRef, - PyRef, PyResult, PyValue, + IntoPyObject, + PyArithmaticValue::{self, *}, + PyClassImpl, PyComparisonValue, PyContext, PyObjectRef, PyRef, PyResult, PyValue, }; use crate::sequence::{self, SimpleSeq}; use crate::vm::{ReprGuard, VirtualMachine}; @@ -111,17 +112,17 @@ impl PyTuple { } #[pymethod(name = "__add__")] - fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyArithmaticValue { if let Some(other) = other.payload_if_subclass::(vm) { - let elements = self + let elements: Vec<_> = self .elements .iter() .chain(other.as_slice().iter()) .cloned() .collect(); - Ok(vm.ctx.new_tuple(elements)) + Implemented(elements.into()) } else { - Ok(vm.ctx.not_implemented()) + NotImplemented } } @@ -190,15 +191,15 @@ impl PyTuple { } #[pymethod(name = "__mul__")] - fn mul(&self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { - let new_elements = sequence::seq_mul(&self.elements, counter) + fn mul(&self, counter: isize, _vm: &VirtualMachine) -> PyTuple { + let new_elements: Vec<_> = sequence::seq_mul(&self.elements, counter) .cloned() .collect(); - vm.ctx.new_tuple(new_elements) + new_elements.into() } #[pymethod(name = "__rmul__")] - fn rmul(&self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { + fn rmul(&self, counter: isize, vm: &VirtualMachine) -> PyTuple { self.mul(counter, vm) } From e5b91c25a86f5e54e72edc8b49ca85e71fec46e7 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 11 Jan 2020 02:22:24 +0900 Subject: [PATCH 3/7] Reduce &String usage --- parser/src/parser.rs | 2 +- vm/src/dictdatatype.rs | 23 +++++------------------ vm/src/pyobject.rs | 24 ++++++++++++------------ 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/parser/src/parser.rs b/parser/src/parser.rs index 7a2ddd248..0f889c5af 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -145,7 +145,7 @@ mod tests { #[test] fn test_parse_empty() { - let parse_ast = parse_program(&String::from("")); + let parse_ast = parse_program(""); assert_eq!(parse_ast, Ok(ast::Program { statements: vec![] })) } diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 2d0b93582..279a41f97 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -369,29 +369,16 @@ impl DictKey for &str { } impl DictKey for &String { - fn do_hash(self, _vm: &VirtualMachine) -> PyResult { - // follow a similar route as the hashing of PyStringRef - let raw_hash = pyhash::hash_value(self).to_bigint().unwrap(); - let raw_hash = pyhash::hash_bigint(&raw_hash); - let mut hasher = DefaultHasher::new(); - raw_hash.hash(&mut hasher); - Ok(hasher.finish() as HashValue) + fn do_hash(self, vm: &VirtualMachine) -> PyResult { + self.as_str().do_hash(vm) } - fn do_is(self, _other: &PyObjectRef) -> bool { - // No matter who the other pyobject is, we are never the same thing, since - // we are a str, not a pyobject. - false + fn do_is(self, other: &PyObjectRef) -> bool { + self.as_str().do_is(other) } fn do_eq(self, vm: &VirtualMachine, other_key: &PyObjectRef) -> PyResult { - if let Some(py_str_value) = other_key.payload::() { - Ok(py_str_value.as_str() == self) - } else { - // Fall back to PyString implementation. - let s = vm.new_str(self.to_string()); - s.do_eq(vm, other_key) - } + self.as_str().do_eq(vm, other_key) } } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 9f38e4328..a02d162c3 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -690,12 +690,6 @@ where } } -impl IntoPyObject for PyRef { - fn into_pyobject(self, _vm: &VirtualMachine) -> PyResult { - Ok(self.obj) - } -} - impl<'a, T: PyValue> From<&'a PyRef> for &'a PyObjectRef { fn from(obj: &'a PyRef) -> Self { obj.as_object() @@ -745,12 +739,6 @@ impl TryFromObject for PyCallable { } } -impl IntoPyObject for PyCallable { - fn into_pyobject(self, _vm: &VirtualMachine) -> PyResult { - Ok(self.into_object()) - } -} - pub trait IdProtocol { fn get_id(&self) -> usize; fn is(&self, other: &T) -> bool @@ -1017,6 +1005,18 @@ pub trait IntoPyObject { fn into_pyobject(self, vm: &VirtualMachine) -> PyResult; } +impl IntoPyObject for PyRef { + fn into_pyobject(self, _vm: &VirtualMachine) -> PyResult { + Ok(self.obj) + } +} + +impl IntoPyObject for PyCallable { + fn into_pyobject(self, _vm: &VirtualMachine) -> PyResult { + Ok(self.into_object()) + } +} + impl IntoPyObject for PyObjectRef { fn into_pyobject(self, _vm: &VirtualMachine) -> PyResult { Ok(self) From 0b1050e8c1e55379ffd12078f4568fbe9a1199f1 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 11 Jan 2020 20:26:18 +0900 Subject: [PATCH 4/7] cleanup PyFunction a little bit --- vm/src/builtins.rs | 2 +- vm/src/obj/objfunction.rs | 6 +++++- vm/src/vm.rs | 18 +++++++----------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 28d1f91c8..6d502dfe9 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -951,7 +951,7 @@ pub fn builtin_build_class_( let cells = vm.ctx.new_dict(); let scope = function - .scope + .scope() .new_child_scope_with_locals(cells.clone()) .new_child_scope_with_locals(namespace.clone()); diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index d308fbf70..f6b1c124f 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -15,7 +15,7 @@ pub type PyFunctionRef = PyRef; pub struct PyFunction { // TODO: these shouldn't be public pub code: PyCodeRef, - pub scope: Scope, + scope: Scope, pub defaults: Option, pub kw_only_defaults: Option, } @@ -49,6 +49,10 @@ impl PyFunction { kw_only_defaults, } } + + pub fn scope(&self) -> &Scope { + &self.scope + } } impl PyValue for PyFunction { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index c13274c89..a34da911d 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -37,7 +37,7 @@ use crate::obj::objiter; use crate::obj::objlist::PyList; use crate::obj::objmodule::{self, PyModule}; use crate::obj::objstr::{PyString, PyStringRef}; -use crate::obj::objtuple::{PyTuple, PyTupleRef}; +use crate::obj::objtuple::PyTuple; use crate::obj::objtype::{self, PyClassRef}; use crate::pyhash; use crate::pyobject::{ @@ -726,7 +726,7 @@ impl VirtualMachine { } pub fn invoke_python_function(&self, func: &PyFunction, func_args: PyFuncArgs) -> PyResult { - self.invoke_python_function_with_scope(func, func_args, &func.scope) + self.invoke_python_function_with_scope(func, func_args, func.scope()) } pub fn invoke_python_function_with_scope( @@ -743,13 +743,7 @@ impl VirtualMachine { scope.clone() }; - self.fill_locals_from_args( - &code, - &scope.get_locals(), - func_args, - &func.defaults, - &func.kw_only_defaults, - )?; + self.fill_locals_from_args(&code, &scope.get_locals(), func_args, func)?; // Construct frame: let frame = Frame::new(code.clone(), scope).into_ref(self); @@ -769,8 +763,7 @@ impl VirtualMachine { code_object: &bytecode::CodeObject, locals: &PyDictRef, func_args: PyFuncArgs, - defaults: &Option, - kw_only_defaults: &Option, + func: &PyFunction, ) -> PyResult<()> { let nargs = func_args.args.len(); let nexpected_args = code_object.arg_names.len(); @@ -849,6 +842,9 @@ impl VirtualMachine { } } + let defaults = &func.defaults; + let kw_only_defaults = &func.kw_only_defaults; + // Add missing positional arguments, if we have fewer positional arguments than the // function definition calls for if nargs < nexpected_args { From 44a17012d6194829907e529a1665029fa98b5e28 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 15 Jan 2020 11:44:19 +0900 Subject: [PATCH 5/7] PyStaticMethodRef -> PyStaticMethod --- vm/src/obj/objstaticmethod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vm/src/obj/objstaticmethod.rs b/vm/src/obj/objstaticmethod.rs index 0fac69822..ea6284130 100644 --- a/vm/src/obj/objstaticmethod.rs +++ b/vm/src/obj/objstaticmethod.rs @@ -29,7 +29,7 @@ impl PyBuiltinDescriptor for PyStaticMethod { } #[pyimpl] -impl PyStaticMethodRef { +impl PyStaticMethod { #[pyslot] fn tp_new( cls: PyClassRef, @@ -44,7 +44,7 @@ impl PyStaticMethodRef { } pub fn init(context: &PyContext) { - PyStaticMethodRef::extend_class(context, &context.types.staticmethod_type); + PyStaticMethod::extend_class(context, &context.types.staticmethod_type); extend_class!(context, context.types.staticmethod_type, { "__get__" => context.new_method(PyStaticMethod::get), (slot descr_get) => PyStaticMethod::get, From 30fc460136eca00fe2624ad15a47f5a3078ddfae Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 15 Jan 2020 11:44:53 +0900 Subject: [PATCH 6/7] PyClassImpl for PyFunction --- vm/src/obj/objfunction.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index f6b1c124f..fa4a1deb5 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -5,12 +5,15 @@ use super::objtuple::PyTupleRef; use super::objtype::PyClassRef; use crate::descriptor::PyBuiltinDescriptor; use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{ + IdProtocol, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, +}; use crate::scope::Scope; use crate::vm::VirtualMachine; pub type PyFunctionRef = PyRef; +#[pyclass] #[derive(Debug)] pub struct PyFunction { // TODO: these shouldn't be public @@ -61,20 +64,25 @@ impl PyValue for PyFunction { } } -impl PyFunctionRef { - fn call(func: PyObjectRef, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult { - vm.invoke(&func, args) +#[pyimpl] +impl PyFunction { + #[pymethod(name = "__call__")] + fn call(zelf: PyObjectRef, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult { + vm.invoke(&zelf, args) } - fn code(self, _vm: &VirtualMachine) -> PyCodeRef { + #[pyproperty(name = "__code__")] + fn code(&self, _vm: &VirtualMachine) -> PyCodeRef { self.code.clone() } - fn defaults(self, _vm: &VirtualMachine) -> Option { + #[pyproperty(name = "__defaults__")] + fn defaults(&self, _vm: &VirtualMachine) -> Option { self.defaults.clone() } - fn kwdefaults(self, _vm: &VirtualMachine) -> Option { + #[pyproperty(name = "__kwdefaults__")] + fn kwdefaults(&self, _vm: &VirtualMachine) -> Option { self.kw_only_defaults.clone() } } @@ -104,13 +112,10 @@ impl PyValue for PyBoundMethod { pub fn init(context: &PyContext) { let function_type = &context.types.function_type; + PyFunction::extend_class(context, function_type); extend_class!(context, function_type, { "__get__" => context.new_method(PyFunction::get), (slot descr_get) => PyFunction::get, - "__call__" => context.new_method(PyFunctionRef::call), - "__code__" => context.new_property(PyFunctionRef::code), - "__defaults__" => context.new_property(PyFunctionRef::defaults), - "__kwdefaults__" => context.new_property(PyFunctionRef::kwdefaults), }); let method_type = &context.types.bound_method_type; From bf0b4dcfddfa85caf35bee16cc4166d204b7d595 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 15 Jan 2020 10:52:02 +0900 Subject: [PATCH 7/7] Hide PyFunction members by moving methods to objfunction --- vm/src/builtins.rs | 2 +- vm/src/obj/objfunction.rs | 186 +++++++++++++++++++++++++++++++++++++- vm/src/vm.rs | 180 +----------------------------------- 3 files changed, 184 insertions(+), 184 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 6d502dfe9..7431ac439 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -955,7 +955,7 @@ pub fn builtin_build_class_( .new_child_scope_with_locals(cells.clone()) .new_child_scope_with_locals(namespace.clone()); - vm.invoke_python_function_with_scope(&function, vec![].into(), &scope)?; + function.invoke_with_scope(vec![].into(), &scope, vm)?; let class = vm.call_method( metaclass.as_object(), diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index fa4a1deb5..10fecf417 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -3,10 +3,15 @@ use super::objdict::PyDictRef; use super::objstr::PyStringRef; use super::objtuple::PyTupleRef; use super::objtype::PyClassRef; +use crate::bytecode; use crate::descriptor::PyBuiltinDescriptor; +use crate::frame::Frame; use crate::function::{OptionalArg, PyFuncArgs}; +use crate::obj::objcoroutine::PyCoroutine; +use crate::obj::objgenerator::PyGenerator; use crate::pyobject::{ - IdProtocol, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + IdProtocol, ItemProtocol, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, + TypeProtocol, }; use crate::scope::Scope; use crate::vm::VirtualMachine; @@ -16,11 +21,10 @@ pub type PyFunctionRef = PyRef; #[pyclass] #[derive(Debug)] pub struct PyFunction { - // TODO: these shouldn't be public - pub code: PyCodeRef, + code: PyCodeRef, scope: Scope, - pub defaults: Option, - pub kw_only_defaults: Option, + defaults: Option, + kw_only_defaults: Option, } impl PyBuiltinDescriptor for PyFunction { @@ -56,6 +60,178 @@ impl PyFunction { pub fn scope(&self) -> &Scope { &self.scope } + + fn fill_locals_from_args( + &self, + code_object: &bytecode::CodeObject, + locals: &PyDictRef, + func_args: PyFuncArgs, + vm: &VirtualMachine, + ) -> PyResult<()> { + let nargs = func_args.args.len(); + let nexpected_args = code_object.arg_names.len(); + + // This parses the arguments from args and kwargs into + // the proper variables keeping into account default values + // and starargs and kwargs. + // See also: PyEval_EvalCodeWithName in cpython: + // https://github.com/python/cpython/blob/master/Python/ceval.c#L3681 + + let n = if nargs > nexpected_args { + nexpected_args + } else { + nargs + }; + + // Copy positional arguments into local variables + for i in 0..n { + let arg_name = &code_object.arg_names[i]; + let arg = &func_args.args[i]; + locals.set_item(arg_name, arg.clone(), vm)?; + } + + // Pack other positional arguments in to *args: + match code_object.varargs { + bytecode::Varargs::Named(ref vararg_name) => { + let mut last_args = vec![]; + for i in n..nargs { + let arg = &func_args.args[i]; + last_args.push(arg.clone()); + } + let vararg_value = vm.ctx.new_tuple(last_args); + + locals.set_item(vararg_name, vararg_value, vm)?; + } + bytecode::Varargs::Unnamed | bytecode::Varargs::None => { + // Check the number of positional arguments + if nargs > nexpected_args { + return Err(vm.new_type_error(format!( + "Expected {} arguments (got: {})", + nexpected_args, nargs + ))); + } + } + } + + // Do we support `**kwargs` ? + let kwargs = match code_object.varkeywords { + bytecode::Varargs::Named(ref kwargs_name) => { + let d = vm.ctx.new_dict(); + locals.set_item(kwargs_name, d.as_object().clone(), vm)?; + Some(d) + } + bytecode::Varargs::Unnamed => Some(vm.ctx.new_dict()), + bytecode::Varargs::None => None, + }; + + // Handle keyword arguments + for (name, value) in func_args.kwargs { + // Check if we have a parameter with this name: + if code_object.arg_names.contains(&name) || code_object.kwonlyarg_names.contains(&name) + { + if locals.contains_key(&name, vm) { + return Err( + vm.new_type_error(format!("Got multiple values for argument '{}'", name)) + ); + } + + locals.set_item(&name, value, vm)?; + } else if let Some(d) = &kwargs { + d.set_item(&name, value, vm)?; + } else { + return Err( + vm.new_type_error(format!("Got an unexpected keyword argument '{}'", name)) + ); + } + } + + // Add missing positional arguments, if we have fewer positional arguments than the + // function definition calls for + if nargs < nexpected_args { + let num_defaults_available = self.defaults.as_ref().map_or(0, |d| d.as_slice().len()); + + // Given the number of defaults available, check all the arguments for which we + // _don't_ have defaults; if any are missing, raise an exception + let required_args = nexpected_args - num_defaults_available; + let mut missing = vec![]; + for i in 0..required_args { + let variable_name = &code_object.arg_names[i]; + if !locals.contains_key(variable_name, vm) { + missing.push(variable_name) + } + } + if !missing.is_empty() { + return Err(vm.new_type_error(format!( + "Missing {} required positional arguments: {:?}", + missing.len(), + missing + ))); + } + if let Some(defaults) = &self.defaults { + let defaults = defaults.as_slice(); + // We have sufficient defaults, so iterate over the corresponding names and use + // the default if we don't already have a value + for (default_index, i) in (required_args..nexpected_args).enumerate() { + let arg_name = &code_object.arg_names[i]; + if !locals.contains_key(arg_name, vm) { + locals.set_item(arg_name, defaults[default_index].clone(), vm)?; + } + } + } + }; + + // Check if kw only arguments are all present: + for arg_name in &code_object.kwonlyarg_names { + if !locals.contains_key(arg_name, vm) { + if let Some(kw_only_defaults) = &self.kw_only_defaults { + if let Some(default) = kw_only_defaults.get_item_option(arg_name, vm)? { + locals.set_item(arg_name, default, vm)?; + continue; + } + } + + // No default value and not specified. + return Err( + vm.new_type_error(format!("Missing required kw only argument: '{}'", arg_name)) + ); + } + } + + Ok(()) + } + + pub fn invoke_with_scope( + &self, + func_args: PyFuncArgs, + scope: &Scope, + vm: &VirtualMachine, + ) -> PyResult { + let code = &self.code; + + let scope = if self.code.flags.contains(bytecode::CodeFlags::NEW_LOCALS) { + scope.new_child_scope(&vm.ctx) + } else { + scope.clone() + }; + + self.fill_locals_from_args(&code, &scope.get_locals(), func_args, vm)?; + + // Construct frame: + let frame = Frame::new(code.clone(), scope).into_ref(vm); + + // If we have a generator, create a new generator + if code.flags.contains(bytecode::CodeFlags::IS_GENERATOR) { + Ok(PyGenerator::new(frame, vm).into_object()) + } else if code.flags.contains(bytecode::CodeFlags::IS_COROUTINE) { + Ok(PyCoroutine::new(frame, vm).into_object()) + } else { + vm.run_frame_full(frame) + } + } + + pub fn invoke(&self, func_args: PyFuncArgs, vm: &VirtualMachine) -> PyResult { + self.invoke_with_scope(func_args, &self.scope, vm) + } } impl PyValue for PyFunction { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index a34da911d..18f2c2596 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -28,10 +28,8 @@ use crate::import; use crate::obj::objbool; use crate::obj::objbuiltinfunc::{PyBuiltinFunction, PyBuiltinMethod}; use crate::obj::objcode::{PyCode, PyCodeRef}; -use crate::obj::objcoroutine::PyCoroutine; use crate::obj::objdict::PyDictRef; use crate::obj::objfunction::{PyBoundMethod, PyFunction}; -use crate::obj::objgenerator::PyGenerator; use crate::obj::objint::PyInt; use crate::obj::objiter; use crate::obj::objlist::PyList; @@ -661,9 +659,9 @@ impl VirtualMachine { fn _invoke(&self, func_ref: &PyObjectRef, args: PyFuncArgs) -> PyResult { vm_trace!("Invoke: {:?} {:?}", func_ref, args); - if let Some(py_func) = func_ref.payload() { + if let Some(py_func) = func_ref.payload::() { self.trace_event(TraceEvent::Call)?; - let res = self.invoke_python_function(py_func, args); + let res = py_func.invoke(args, self); self.trace_event(TraceEvent::Return)?; res } else if let Some(PyBoundMethod { @@ -725,180 +723,6 @@ impl VirtualMachine { Ok(()) } - pub fn invoke_python_function(&self, func: &PyFunction, func_args: PyFuncArgs) -> PyResult { - self.invoke_python_function_with_scope(func, func_args, func.scope()) - } - - pub fn invoke_python_function_with_scope( - &self, - func: &PyFunction, - func_args: PyFuncArgs, - scope: &Scope, - ) -> PyResult { - let code = &func.code; - - let scope = if func.code.flags.contains(bytecode::CodeFlags::NEW_LOCALS) { - scope.new_child_scope(&self.ctx) - } else { - scope.clone() - }; - - self.fill_locals_from_args(&code, &scope.get_locals(), func_args, func)?; - - // Construct frame: - let frame = Frame::new(code.clone(), scope).into_ref(self); - - // If we have a generator, create a new generator - if code.flags.contains(bytecode::CodeFlags::IS_GENERATOR) { - Ok(PyGenerator::new(frame, self).into_object()) - } else if code.flags.contains(bytecode::CodeFlags::IS_COROUTINE) { - Ok(PyCoroutine::new(frame, self).into_object()) - } else { - self.run_frame_full(frame) - } - } - - fn fill_locals_from_args( - &self, - code_object: &bytecode::CodeObject, - locals: &PyDictRef, - func_args: PyFuncArgs, - func: &PyFunction, - ) -> PyResult<()> { - let nargs = func_args.args.len(); - let nexpected_args = code_object.arg_names.len(); - - // This parses the arguments from args and kwargs into - // the proper variables keeping into account default values - // and starargs and kwargs. - // See also: PyEval_EvalCodeWithName in cpython: - // https://github.com/python/cpython/blob/master/Python/ceval.c#L3681 - - let n = if nargs > nexpected_args { - nexpected_args - } else { - nargs - }; - - // Copy positional arguments into local variables - for i in 0..n { - let arg_name = &code_object.arg_names[i]; - let arg = &func_args.args[i]; - locals.set_item(arg_name, arg.clone(), self)?; - } - - // Pack other positional arguments in to *args: - match code_object.varargs { - bytecode::Varargs::Named(ref vararg_name) => { - let mut last_args = vec![]; - for i in n..nargs { - let arg = &func_args.args[i]; - last_args.push(arg.clone()); - } - let vararg_value = self.ctx.new_tuple(last_args); - - locals.set_item(vararg_name, vararg_value, self)?; - } - bytecode::Varargs::Unnamed | bytecode::Varargs::None => { - // Check the number of positional arguments - if nargs > nexpected_args { - return Err(self.new_type_error(format!( - "Expected {} arguments (got: {})", - nexpected_args, nargs - ))); - } - } - } - - // Do we support `**kwargs` ? - let kwargs = match code_object.varkeywords { - bytecode::Varargs::Named(ref kwargs_name) => { - let d = self.ctx.new_dict(); - locals.set_item(kwargs_name, d.as_object().clone(), self)?; - Some(d) - } - bytecode::Varargs::Unnamed => Some(self.ctx.new_dict()), - bytecode::Varargs::None => None, - }; - - // Handle keyword arguments - for (name, value) in func_args.kwargs { - // Check if we have a parameter with this name: - if code_object.arg_names.contains(&name) || code_object.kwonlyarg_names.contains(&name) - { - if locals.contains_key(&name, self) { - return Err( - self.new_type_error(format!("Got multiple values for argument '{}'", name)) - ); - } - - locals.set_item(&name, value, self)?; - } else if let Some(d) = &kwargs { - d.set_item(&name, value, self)?; - } else { - return Err( - self.new_type_error(format!("Got an unexpected keyword argument '{}'", name)) - ); - } - } - - let defaults = &func.defaults; - let kw_only_defaults = &func.kw_only_defaults; - - // Add missing positional arguments, if we have fewer positional arguments than the - // function definition calls for - if nargs < nexpected_args { - let num_defaults_available = defaults.as_ref().map_or(0, |d| d.as_slice().len()); - - // Given the number of defaults available, check all the arguments for which we - // _don't_ have defaults; if any are missing, raise an exception - let required_args = nexpected_args - num_defaults_available; - let mut missing = vec![]; - for i in 0..required_args { - let variable_name = &code_object.arg_names[i]; - if !locals.contains_key(variable_name, self) { - missing.push(variable_name) - } - } - if !missing.is_empty() { - return Err(self.new_type_error(format!( - "Missing {} required positional arguments: {:?}", - missing.len(), - missing - ))); - } - if let Some(defaults) = defaults { - let defaults = defaults.as_slice(); - // We have sufficient defaults, so iterate over the corresponding names and use - // the default if we don't already have a value - for (default_index, i) in (required_args..nexpected_args).enumerate() { - let arg_name = &code_object.arg_names[i]; - if !locals.contains_key(arg_name, self) { - locals.set_item(arg_name, defaults[default_index].clone(), self)?; - } - } - } - }; - - // Check if kw only arguments are all present: - for arg_name in &code_object.kwonlyarg_names { - if !locals.contains_key(arg_name, self) { - if let Some(kw_only_defaults) = kw_only_defaults { - if let Some(default) = kw_only_defaults.get_item_option(arg_name, self)? { - locals.set_item(arg_name, default, self)?; - continue; - } - } - - // No default value and not specified. - return Err(self - .new_type_error(format!("Missing required kw only argument: '{}'", arg_name))); - } - } - - Ok(()) - } - pub fn extract_elements(&self, value: &PyObjectRef) -> PyResult> { // Extract elements from item, if possible: let cls = value.class();