diff --git a/src/main.rs b/src/main.rs index fb9be6bce8..8a3fde63db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -127,7 +127,8 @@ fn shell_exec(vm: &mut VirtualMachine, source: &str, scope: PyObjectRef) -> bool } Err(err) => { // Enum rather than special string here. - let msg = match vm.get_attribute(err.clone(), "msg") { + let name = vm.new_str("msg".to_string()); + let msg = match vm.get_attribute(err.clone(), name) { Ok(value) => objstr::get_value(&value), Err(_) => panic!("Expected msg attribute on exception object!"), }; diff --git a/tests/snippets/metaclasses.py b/tests/snippets/metaclasses.py new file mode 100644 index 0000000000..864b1446b9 --- /dev/null +++ b/tests/snippets/metaclasses.py @@ -0,0 +1,25 @@ +class MC(type): + classes = [] + count = 0 + + def __new__(cls, name, bases, namespace): + MC.classes.append(name) + return type.__new__(cls, name, bases, namespace) + + def __call__(cls): + MC.count += 1 + return type.__call__(cls, MC.count) + +class C(object, metaclass=MC): + def __new__(cls, count): + self = object.__new__(cls) + self.count = count + return self + +class D(object, metaclass=MC): + pass + +assert MC == type(C) +assert C == type(C()) +assert MC.classes == ['C', 'D'] +assert C().count == 2 diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 8b7570b951..7e6ecfd4cc 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -50,7 +50,7 @@ fn dir_object(vm: &mut VirtualMachine, obj: &PyObjectRef) -> PyObjectRef { fn builtin_abs(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(x, None)]); - match vm.get_attribute(x.clone(), &"__abs__") { + match vm.get_method(x.clone(), "__abs__") { Ok(attrib) => vm.invoke(attrib, PyFuncArgs::new(vec![], vec![])), Err(..) => Err(vm.new_type_error("bad operand for abs".to_string())), } @@ -134,7 +134,7 @@ fn builtin_dir(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn builtin_divmod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(x, None), (y, None)]); - match vm.get_attribute(x.clone(), &"__divmod__") { + match vm.get_method(x.clone(), "__divmod__") { Ok(attrib) => vm.invoke(attrib, PyFuncArgs::new(vec![y.clone()], vec![])), Err(..) => Err(vm.new_type_error("unsupported operand type(s) for divmod".to_string())), } @@ -218,11 +218,7 @@ fn builtin_getattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { args, required = [(obj, None), (attr, Some(vm.ctx.str_type()))] ); - if let PyObjectKind::String { ref value } = attr.borrow().kind { - vm.get_attribute(obj.clone(), value) - } else { - panic!("argument checking failure: attr not string") - } + vm.get_attribute(obj.clone(), attr.clone()) } // builtin_globals @@ -233,15 +229,11 @@ fn builtin_hasattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { args, required = [(obj, None), (attr, Some(vm.ctx.str_type()))] ); - if let PyObjectKind::String { ref value } = attr.borrow().kind { - let has_attr = match vm.get_attribute(obj.clone(), value) { - Ok(..) => true, - Err(..) => false, - }; - Ok(vm.context().new_bool(has_attr)) - } else { - panic!("argument checking failure: attr not string") - } + let has_attr = match vm.get_attribute(obj.clone(), attr.clone()) { + Ok(..) => true, + Err(..) => false, + }; + Ok(vm.context().new_bool(has_attr)) } fn builtin_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -308,7 +300,7 @@ fn builtin_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } _ => { let len_method_name = "__len__".to_string(); - match vm.get_attribute(obj.clone(), &len_method_name) { + match vm.get_method(obj.clone(), &len_method_name) { Ok(value) => vm.invoke(value, PyFuncArgs::default()), Err(..) => Err(vm.context().new_str( format!( @@ -444,7 +436,7 @@ fn builtin_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { optional = [(mod_value, Some(vm.ctx.int_type()))] ); let pow_method_name = "__pow__".to_string(); - let result = match vm.get_attribute(x.clone(), &pow_method_name) { + let result = match vm.get_method(x.clone(), &pow_method_name) { Ok(attrib) => vm.invoke(attrib, PyFuncArgs::new(vec![y.clone()], vec![])), Err(..) => Err(vm.new_type_error("unsupported operand type(s) for pow".to_string())), }; @@ -454,7 +446,7 @@ fn builtin_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { match mod_value { Some(mod_value) => { let mod_method_name = "__mod__".to_string(); - match vm.get_attribute( + match vm.get_method( result.expect("result not defined").clone(), &mod_method_name, ) { @@ -644,14 +636,10 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { pub fn builtin_build_class_(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> PyResult { let function = args.shift(); let name_arg = args.shift(); - let name_arg_ref = name_arg.borrow(); - let name = match name_arg_ref.kind { - PyObjectKind::String { ref value } => value, - _ => panic!("Class name must by a string!"), - }; let mut bases = args.args.clone(); + let metaclass = args.get_kwarg("metaclass", vm.get_type()); + bases.push(vm.context().object()); - let metaclass = vm.get_type(); let namespace = vm.new_dict(); &vm.invoke( function, @@ -660,5 +648,18 @@ pub fn builtin_build_class_(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> Py kwargs: vec![], }, ); - objtype::new(metaclass, name, bases, namespace) + + let bases = vm.context().new_tuple(bases); + + // Special case: __new__ must be looked up on the metaclass, not the meta-metaclass as + // per vm.call(metaclass, "__new__", ...) + let new = metaclass.get_attr("__new__").unwrap(); + let wrapped = vm.call_get_descriptor(new, metaclass)?; + vm.invoke( + wrapped, + PyFuncArgs { + args: vec![name_arg, bases, namespace], + kwargs: vec![], + }, + ) } diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 132438fa38..c72a8ad29e 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -436,7 +436,7 @@ impl Compiler { name, body, bases, - keywords: _, + keywords, decorator_list, } => { self.prepare_decorators(decorator_list)?; @@ -483,9 +483,34 @@ impl Compiler { for base in bases { self.compile_expression(base)?; } - self.emit(Instruction::CallFunction { - typ: CallType::Positional(2 + bases.len()), - }); + + if keywords.len() > 0 { + let mut kwarg_names = vec![]; + for keyword in keywords { + if let Some(name) = &keyword.name { + kwarg_names.push(bytecode::Constant::String { + value: name.to_string(), + }); + } else { + // This means **kwargs! + panic!("name must be set"); + } + self.compile_expression(&keyword.value)?; + } + + self.emit(Instruction::LoadConst { + value: bytecode::Constant::Tuple { + elements: kwarg_names, + }, + }); + self.emit(Instruction::CallFunction { + typ: CallType::Keyword(2 + keywords.len() + bases.len()), + }); + } else { + self.emit(Instruction::CallFunction { + typ: CallType::Positional(2 + bases.len()), + }); + } self.apply_decorators(decorator_list); diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 3112f16666..2a033f0c68 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -123,9 +123,8 @@ impl Frame { &exception, &vm.ctx.exceptions.base_exception_type )); - let traceback = vm - .get_attribute(exception.clone(), &"__traceback__".to_string()) - .unwrap(); + let traceback_name = vm.new_str("__traceback__".to_string()); + let traceback = vm.get_attribute(exception.clone(), traceback_name).unwrap(); trace!("Adding to traceback: {:?} {:?}", traceback, lineno); let pos = vm.ctx.new_tuple(vec![ vm.ctx.new_str(filename.clone()), @@ -1007,6 +1006,7 @@ impl Frame { fn load_attr(&mut self, vm: &mut VirtualMachine, attr_name: &str) -> FrameResult { let parent = self.pop_value(); + let attr_name = vm.new_str(attr_name.to_string()); let obj = vm.get_attribute(parent, attr_name)?; self.push_value(obj); Ok(None) diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index 6f441ff419..4d9404f58b 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -15,7 +15,7 @@ pub fn boolval(vm: &mut VirtualMachine, obj: PyObjectRef) -> Result !value.is_empty(), PyObjectKind::None { .. } => false, _ => { - if let Ok(f) = objtype::get_attribute(vm, obj.clone(), &String::from("__bool__")) { + if let Ok(f) = vm.get_method(obj.clone(), "__bool__") { let bool_res = vm.invoke(f, PyFuncArgs::default())?; let v = match bool_res.borrow().kind { PyObjectKind::Integer { ref value } => !value.is_zero(), diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 8e1aa37e14..070f611284 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -5,6 +5,7 @@ use super::super::pyobject::{ use super::super::vm::VirtualMachine; use super::objbool; use super::objdict; +use super::objstr; use super::objtype; pub fn new_instance(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> PyResult { @@ -15,12 +16,6 @@ pub fn new_instance(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> PyResult { Ok(obj) } -pub fn call(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> PyResult { - let instance = args.shift(); - let function = objtype::get_attribute(vm, instance, &String::from("__call__"))?; - vm.invoke(function, args) -} - pub fn create_object(type_type: PyObjectRef, object_type: PyObjectRef, dict_type: PyObjectRef) { (*object_type.borrow_mut()).kind = PyObjectKind::Class { name: String::from("object"), @@ -101,6 +96,10 @@ pub fn init(context: &PyContext) { object.set_attr("__hash__", context.new_rustfunc(object_hash)); object.set_attr("__str__", context.new_rustfunc(object_str)); object.set_attr("__repr__", context.new_rustfunc(object_repr)); + object.set_attr( + "__getattribute__", + context.new_rustfunc(object_getattribute), + ); } fn object_init(vm: &mut VirtualMachine, _args: PyFuncArgs) -> PyResult { @@ -114,3 +113,54 @@ fn object_dict(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { _ => Err(vm.new_type_error("TypeError: no dictionary.".to_string())), } } + +fn object_getattribute(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [ + (obj, Some(vm.ctx.object())), + (name_str, Some(vm.ctx.str_type())) + ] + ); + let name = objstr::get_value(&name_str); + trace!("object.__getattribute__({:?}, {:?})", obj, name); + let cls = obj.typ(); + + if let Some(attr) = cls.get_attr(&name) { + let attr_class = attr.typ(); + if attr_class.has_attr("__set__") { + if let Some(descriptor) = attr_class.get_attr("__get__") { + return vm.invoke( + descriptor, + PyFuncArgs { + args: vec![attr, obj.clone(), cls], + kwargs: vec![], + }, + ); + } + } + } + + if let Some(obj_attr) = obj.get_attr(&name) { + Ok(obj_attr) + } else if let Some(attr) = cls.get_attr(&name) { + vm.call_get_descriptor(attr, obj.clone()) + } else { + if let Some(getter) = cls.get_attr("__getattr__") { + vm.invoke( + getter, + PyFuncArgs { + args: vec![cls, name_str.clone()], + kwargs: vec![], + }, + ) + } else { + let attribute_error = vm.context().exceptions.attribute_error.clone(); + Err(vm.new_exception( + attribute_error, + format!("{:?} object has no attribute {}", cls, name), + )) + } + } +} diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 6c0ff19c04..b417050247 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -4,6 +4,7 @@ use super::super::pyobject::{ }; use super::super::vm::VirtualMachine; use super::objdict; +use super::objstr; use super::objtype; // Required for arg_check! to use isinstance use std::collections::HashMap; @@ -137,32 +138,68 @@ pub fn type_call(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> PyResult { Ok(obj) } -pub fn get_attribute(vm: &mut VirtualMachine, obj: PyObjectRef, name: &str) -> PyResult { - let cls = obj.typ(); - trace!("get_attribute: {:?}, {:?}, {:?}", cls, obj, name); - if let Some(attr) = cls.get_attr(name) { +pub fn type_getattribute(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [ + (cls, Some(vm.ctx.object())), + (name_str, Some(vm.ctx.str_type())) + ] + ); + let name = objstr::get_value(&name_str); + trace!("type.__getattribute__({:?}, {:?})", cls, name); + let mcl = cls.typ(); + + if let Some(attr) = mcl.get_attr(&name) { + let attr_class = attr.typ(); + if attr_class.has_attr("__set__") { + if let Some(descriptor) = attr_class.get_attr("__get__") { + return vm.invoke( + descriptor, + PyFuncArgs { + args: vec![attr, cls.clone(), mcl], + kwargs: vec![], + }, + ); + } + } + } + + if let Some(attr) = cls.get_attr(&name) { let attr_class = attr.typ(); if let Some(descriptor) = attr_class.get_attr("__get__") { + let none = vm.get_none(); return vm.invoke( descriptor, PyFuncArgs { - args: vec![attr, obj, cls], + args: vec![attr, none, cls.clone()], kwargs: vec![], }, ); } } - if let Some(obj_attr) = obj.get_attr(name) { - Ok(obj_attr) - } else if let Some(cls_attr) = cls.get_attr(name) { + if let Some(cls_attr) = cls.get_attr(&name) { Ok(cls_attr) + } else if let Some(attr) = mcl.get_attr(&name) { + vm.call_get_descriptor(attr, cls.clone()) } else { - let attribute_error = vm.context().exceptions.attribute_error.clone(); - Err(vm.new_exception( - attribute_error, - format!("{:?} object has no attribute {}", cls, name), - )) + if let Some(getter) = cls.get_attr("__getattr__") { + vm.invoke( + getter, + PyFuncArgs { + args: vec![mcl, name_str.clone()], + kwargs: vec![], + }, + ) + } else { + let attribute_error = vm.context().exceptions.attribute_error.clone(); + Err(vm.new_exception( + attribute_error, + format!("{:?} object {:?} has no attribute {}", mcl, cls, name), + )) + } } } @@ -263,11 +300,6 @@ fn type_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_str(format!("", type_name))) } -pub fn call(vm: &mut VirtualMachine, typ: PyObjectRef, args: PyFuncArgs) -> PyResult { - let function = get_attribute(vm, typ, &String::from("__call__"))?; - vm.invoke(function, args) -} - #[cfg(test)] mod tests { use super::{linearise_mro, new}; diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 42f3506059..4dd2b7e05e 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -634,6 +634,15 @@ impl PyFuncArgs { pub fn shift(&mut self) -> PyObjectRef { self.args.remove(0) } + + pub fn get_kwarg(&self, key: &str, default: PyObjectRef) -> PyObjectRef { + for (arg_name, arg_value) in self.kwargs.iter() { + if arg_name == key { + return arg_value.clone(); + } + } + default.clone() + } } type RustPyFunc = fn(vm: &mut VirtualMachine, PyFuncArgs) -> PyResult; diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 50fab0c51c..6ea7957cc4 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -14,10 +14,12 @@ use super::frame::{copy_code, Frame}; use super::obj::objgenerator; use super::obj::objiter; use super::obj::objlist; -use super::obj::objobject; use super::obj::objtuple; use super::obj::objtype; -use super::pyobject::{DictProtocol, PyContext, PyFuncArgs, PyObjectKind, PyObjectRef, PyResult}; +use super::pyobject::{ + AttributeProtocol, DictProtocol, PyContext, PyFuncArgs, PyObjectKind, PyObjectRef, PyResult, + TypeProtocol, +}; use super::stdlib; use super::sysmodule; @@ -147,18 +149,54 @@ impl VirtualMachine { self.call_method(obj, "__repr__", vec![]) } + pub fn call_get_descriptor(&mut self, attr: PyObjectRef, obj: PyObjectRef) -> PyResult { + let attr_class = attr.typ(); + if let Some(descriptor) = attr_class.get_attr("__get__") { + let cls = obj.typ(); + self.invoke( + descriptor, + PyFuncArgs { + args: vec![attr, obj.clone(), cls], + kwargs: vec![], + }, + ) + } else { + Ok(attr) + } + } + pub fn call_method( &mut self, obj: &PyObjectRef, method_name: &str, args: Vec, ) -> PyResult { - let func = self.get_attribute(obj.clone(), method_name)?; - let args = PyFuncArgs { - args: args, - kwargs: vec![], - }; - self.invoke(func, args) + self.call_method_pyargs( + obj, + method_name, + PyFuncArgs { + args: args, + kwargs: vec![], + }, + ) + } + + pub fn call_method_pyargs( + &mut self, + obj: &PyObjectRef, + method_name: &str, + args: PyFuncArgs, + ) -> PyResult { + // This is only used in the vm for magic methods, which use a greatly simplified attribute lookup. + let cls = obj.typ(); + match cls.get_attr(method_name) { + Some(func) => { + trace!("vm.call_method {:?} {:?} -> {:?}", obj, method_name, func); + let wrapped = self.call_get_descriptor(func, obj.clone())?; + self.invoke(wrapped, args) + } + None => Err(self.new_type_error(format!("Unsupported method: {}", method_name))), + } } pub fn invoke(&mut self, func_ref: PyObjectRef, args: PyFuncArgs) -> PyResult { @@ -174,12 +212,12 @@ impl VirtualMachine { name: _, dict: _, mro: _, - } => objtype::call(self, func_ref.clone(), args), + } => self.call_method_pyargs(&func_ref, "__call__", args), PyObjectKind::BoundMethod { ref function, ref object, } => self.invoke(function.clone(), args.insert(object.clone())), - PyObjectKind::Instance { .. } => objobject::call(self, args.insert(func_ref.clone())), + PyObjectKind::Instance { .. } => self.call_method_pyargs(&func_ref, "__call__", args), ref kind => { unimplemented!("invoke unimplemented for: {:?}", kind); } @@ -370,8 +408,22 @@ impl VirtualMachine { Ok(elements) } - pub fn get_attribute(&mut self, obj: PyObjectRef, attr_name: &str) -> PyResult { - objtype::get_attribute(self, obj.clone(), attr_name) + // get_attribute should be used for full attribute access (usually from user code). + pub fn get_attribute(&mut self, obj: PyObjectRef, attr_name: PyObjectRef) -> PyResult { + self.call_method(&obj, "__getattribute__", vec![attr_name]) + } + + // get_method should be used for internal access to magic methods (by-passing + // the full getattribute look-up. + pub fn get_method(&mut self, obj: PyObjectRef, method_name: &str) -> PyResult { + let cls = obj.typ(); + match cls.get_attr(method_name) { + Some(method) => self.call_get_descriptor(method, obj.clone()), + None => { + Err(self + .new_type_error(format!("{:?} object has no method {:?}", obj, method_name))) + } + } } pub fn _sub(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult {