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/builtins.rs b/vm/src/builtins.rs index 28d1f91c8..7431ac439 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -951,11 +951,11 @@ 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()); - 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/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/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/obj/objfunction.rs b/vm/src/obj/objfunction.rs index d308fbf70..10fecf417 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -3,21 +3,28 @@ 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::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::obj::objcoroutine::PyCoroutine; +use crate::obj::objgenerator::PyGenerator; +use crate::pyobject::{ + IdProtocol, ItemProtocol, 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 - pub code: PyCodeRef, - pub scope: Scope, - pub defaults: Option, - pub kw_only_defaults: Option, + code: PyCodeRef, + scope: Scope, + defaults: Option, + kw_only_defaults: Option, } impl PyBuiltinDescriptor for PyFunction { @@ -49,6 +56,182 @@ impl PyFunction { kw_only_defaults, } } + + 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 { @@ -57,20 +240,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() } } @@ -100,13 +288,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; 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, 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) } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 3dbd96f70..a02d162c3 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, ) @@ -692,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() @@ -747,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 @@ -1019,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) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index c13274c89..18f2c2596 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -28,16 +28,14 @@ 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; 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::{ @@ -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,184 +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.defaults, - &func.kw_only_defaults, - )?; - - // 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, - defaults: &Option, - kw_only_defaults: &Option, - ) -> 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)) - ); - } - } - - // 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();