From dce45825ae0b718b5128cb13a069b90c9da97267 Mon Sep 17 00:00:00 2001 From: jfh Date: Fri, 15 Oct 2021 16:27:27 +0300 Subject: [PATCH 1/2] Allow any mapping for locals. --- Lib/test/test_builtin.py | 2 -- Lib/test/test_compile.py | 2 -- vm/src/builtins/frame.rs | 2 +- vm/src/builtins/function.rs | 3 ++- vm/src/frame.rs | 54 ++++++++++++++++++++++--------------- vm/src/scope.rs | 10 +++---- vm/src/stdlib/builtins.rs | 28 +++++++++++-------- vm/src/vm.rs | 4 +-- 8 files changed, 59 insertions(+), 46 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index d67bcd783e..341acc70cf 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -490,8 +490,6 @@ def __getitem__(self, key): raise ValueError self.assertRaises(ValueError, eval, "foo", {}, X()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_general_eval(self): # Tests that general mappings can be used for the locals argument diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 3cb3cc9fd9..615a600ab9 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -61,8 +61,6 @@ def test_none_keyword_arg(self): def test_duplicate_global_local(self): self.assertRaises(SyntaxError, exec, 'def f(a): global a; a = 1') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exec_with_general_mapping_for_locals(self): class M: diff --git a/vm/src/builtins/frame.rs b/vm/src/builtins/frame.rs index 1655f857af..1f7238482a 100644 --- a/vm/src/builtins/frame.rs +++ b/vm/src/builtins/frame.rs @@ -44,7 +44,7 @@ impl FrameRef { } #[pyproperty] - fn f_locals(self, vm: &VirtualMachine) -> PyResult { + fn f_locals(self, vm: &VirtualMachine) -> PyResult { self.locals(vm) } diff --git a/vm/src/builtins/function.rs b/vm/src/builtins/function.rs index 66574f0be1..7f6412477d 100644 --- a/vm/src/builtins/function.rs +++ b/vm/src/builtins/function.rs @@ -290,7 +290,8 @@ impl PyFunction { vm.ctx.new_dict() } else { locals.unwrap_or_else(|| self.globals.clone()) - }; + } + .into(); // Construct frame: let frame = Frame::new( diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 6c6598f805..a35933fbf3 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -99,7 +99,7 @@ pub struct Frame { pub fastlocals: PyMutex]>>, pub(crate) cells_frees: Box<[PyCellRef]>, - pub locals: PyDictRef, + pub locals: PyObjectRef, pub globals: PyDictRef, pub builtins: PyDictRef, @@ -179,7 +179,7 @@ impl FrameRef { f(exec) } - pub fn locals(&self, vm: &VirtualMachine) -> PyResult { + pub fn locals(&self, vm: &VirtualMachine) -> PyResult { let locals = &self.locals; let code = &**self.code; let map = &code.varnames; @@ -275,7 +275,7 @@ struct ExecutingFrame<'a> { code: &'a PyRef, fastlocals: &'a PyMutex]>>, cells_frees: &'a [PyCellRef], - locals: &'a PyDictRef, + locals: &'a PyObjectRef, globals: &'a PyDictRef, builtins: &'a PyDictRef, object: &'a FrameRef, @@ -496,12 +496,15 @@ impl ExecutingFrame<'_> { } bytecode::Instruction::LoadNameAny(idx) => { let name = &self.code.names[*idx as usize]; - let x = self.locals.get_item_option(name.clone(), vm)?; - let x = match x { + // Try using locals as dict first, if not, fallback to generic method. + let x = match self.locals.clone().downcast_exact::(vm) { + Ok(d) => d.get_item_option(name.clone(), vm)?, + Err(o) => o.get_item(name.clone(), vm).ok(), + }; + self.push_value(match x { Some(x) => x, None => self.load_global_or_builtin(name, vm)?, - }; - self.push_value(x); + }); Ok(None) } bytecode::Instruction::LoadGlobal(idx) => { @@ -521,14 +524,17 @@ impl ExecutingFrame<'_> { bytecode::Instruction::LoadClassDeref(i) => { let i = *i as usize; let name = self.code.freevars[i - self.code.cellvars.len()].clone(); - let value = if let Some(value) = self.locals.get_item_option(name, vm)? { - value - } else { - self.cells_frees[i] - .get() - .ok_or_else(|| self.unbound_cell_exception(i, vm))? + // Try using locals as dict first, if not, fallback to generic method. + let value = match self.locals.clone().downcast_exact::(vm) { + Ok(d) => d.get_item_option(name, vm)?, + Err(o) => o.get_item(name, vm).ok(), }; - self.push_value(value); + self.push_value(match value { + Some(v) => v, + None => self.cells_frees[i] + .get() + .ok_or_else(|| self.unbound_cell_exception(i, vm))?, + }); Ok(None) } bytecode::Instruction::StoreFast(idx) => { @@ -717,7 +723,15 @@ impl ExecutingFrame<'_> { } bytecode::Instruction::YieldFrom => self.execute_yield_from(vm), bytecode::Instruction::SetupAnnotation => { - if !self.locals.contains_key("__annotations__", vm) { + // Try using locals as dict first, if not, fallback to generic method. + let has_annotations = match self.locals.clone().downcast_exact::(vm) { + Ok(d) => d.contains_key("__annotations__", vm), + Err(o) => { + let needle = vm.new_pyobj("__annotations__"); + self._in(vm, needle, o)? + } + }; + if !has_annotations { self.locals .set_item("__annotations__", vm.ctx.new_dict().into(), vm)?; } @@ -1822,15 +1836,11 @@ impl fmt::Debug for Frame { .map(|elem| format!("\n > {:?}", elem)) .collect::(); // TODO: fix this up - let dict = self.locals.clone(); - let local_str = dict - .into_iter() - .map(|elem| format!("\n {:?} = {:?}", elem.0, elem.1)) - .collect::(); + let locals = self.locals.clone(); write!( f, - "Frame Object {{ \n Stack:{}\n Blocks:{}\n Locals:{}\n}}", - stack_str, block_str, local_str + "Frame Object {{ \n Stack:{}\n Blocks:{}\n Locals:{:?}\n}}", + stack_str, block_str, locals ) } } diff --git a/vm/src/scope.rs b/vm/src/scope.rs index aa004d080f..b67f083773 100644 --- a/vm/src/scope.rs +++ b/vm/src/scope.rs @@ -1,13 +1,13 @@ use crate::{ builtins::{pystr::IntoPyStrRef, PyDictRef, PyStrRef}, function::IntoPyObject, - ItemProtocol, VirtualMachine, + ItemProtocol, PyObjectRef, VirtualMachine, }; use std::fmt; #[derive(Clone)] pub struct Scope { - pub locals: PyDictRef, + pub locals: PyObjectRef, pub globals: PyDictRef, } @@ -20,13 +20,13 @@ impl fmt::Debug for Scope { impl Scope { #[inline] - pub fn new(locals: Option, globals: PyDictRef) -> Scope { - let locals = locals.unwrap_or_else(|| globals.clone()); + pub fn new(locals: Option, globals: PyDictRef) -> Scope { + let locals = locals.unwrap_or_else(|| globals.clone().into()); Scope { locals, globals } } pub fn with_builtins( - locals: Option, + locals: Option, globals: PyDictRef, vm: &VirtualMachine, ) -> Scope { diff --git a/vm/src/stdlib/builtins.rs b/vm/src/stdlib/builtins.rs index 30b2c19a9d..b636fdb208 100644 --- a/vm/src/stdlib/builtins.rs +++ b/vm/src/stdlib/builtins.rs @@ -25,7 +25,7 @@ mod builtins { ArgBytesLike, ArgCallable, ArgIntoBool, ArgIterable, FuncArgs, KwArgs, OptionalArg, OptionalOption, PosArgs, }, - protocol::{PyIter, PyIterReturn}, + protocol::{PyIter, PyIterReturn, PyMapping}, py_io, readline::{Readline, ReadlineResult}, scope::Scope, @@ -206,28 +206,34 @@ mod builtins { globals: Option, // TODO: support any mapping for `locals` #[pyarg(any, default)] - locals: Option, + locals: Option, } #[cfg(feature = "rustpython-compiler")] impl ScopeArgs { fn make_scope(self, vm: &VirtualMachine) -> PyResult { + // TODO: Other places where user supplies locals object? + let locals = self.locals; + let get_locals = |default| { + locals.map_or(Ok(default), |locals| { + if !PyMapping::check(&locals, vm) { + Err(vm.new_type_error("locals must be a mapping".to_owned())) + } else { + Ok(locals) + } + }) + }; let (globals, locals) = match self.globals { Some(globals) => { if !globals.contains_key("__builtins__", vm) { let builtins_dict = vm.builtins.dict().unwrap().into(); globals.set_item("__builtins__", builtins_dict, vm)?; } - let locals = self.locals.unwrap_or_else(|| globals.clone()); - (globals, locals) + (globals.clone(), get_locals(globals.into())?) } None => { let globals = vm.current_globals().clone(); - let locals = match self.locals { - Some(l) => l, - None => vm.current_locals()?, - }; - (globals, locals) + (globals, vm.current_locals().and_then(get_locals)?) } }; @@ -426,7 +432,7 @@ mod builtins { } #[pyfunction] - fn locals(vm: &VirtualMachine) -> PyResult { + fn locals(vm: &VirtualMachine) -> PyResult { vm.current_locals() } @@ -793,7 +799,7 @@ mod builtins { vm.new_type_error("vars() argument must have __dict__ attribute".to_owned()) }) } else { - Ok(vm.current_locals()?.into()) + Ok(vm.current_locals()?) } } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 1243470561..2f7d82c0ac 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -558,7 +558,7 @@ impl VirtualMachine { } } - pub fn current_locals(&self) -> PyResult { + pub fn current_locals(&self) -> PyResult { self.current_frame() .expect("called current_locals but no frames on the stack") .locals(self) @@ -1201,7 +1201,7 @@ impl VirtualMachine { .get_special_method(obj, "__dir__")? .map_err(|_obj| self.new_type_error("object does not provide __dir__".to_owned()))? .invoke((), self)?, - None => self.call_method(self.current_locals()?.as_object(), "keys", ())?, + None => self.call_method(&self.current_locals()?, "keys", ())?, }; let items = self.extract_elements(&seq)?; let lst = PyList::from(items); From 94b91632978bed542800b8fc4c65f6c3a3728329 Mon Sep 17 00:00:00 2001 From: jfh Date: Fri, 15 Oct 2021 20:16:43 +0300 Subject: [PATCH 2/2] Use PyMapping, fix handling of __prepare__. --- vm/src/builtins/frame.rs | 3 +- vm/src/builtins/function.rs | 12 +++-- vm/src/frame.rs | 95 +++++++++++++++++++++++++++++-------- vm/src/scope.rs | 11 +++-- vm/src/stdlib/builtins.rs | 53 +++++++++++---------- vm/src/vm.rs | 8 ++-- 6 files changed, 124 insertions(+), 58 deletions(-) diff --git a/vm/src/builtins/frame.rs b/vm/src/builtins/frame.rs index 1f7238482a..cd338fe4c3 100644 --- a/vm/src/builtins/frame.rs +++ b/vm/src/builtins/frame.rs @@ -5,6 +5,7 @@ use super::{PyCode, PyDictRef, PyStrRef}; use crate::{ frame::{Frame, FrameRef}, + protocol::PyMapping, types::{Constructor, Unconstructible}, IdProtocol, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, VirtualMachine, }; @@ -44,7 +45,7 @@ impl FrameRef { } #[pyproperty] - fn f_locals(self, vm: &VirtualMachine) -> PyResult { + fn f_locals(self, vm: &VirtualMachine) -> PyResult { self.locals(vm) } diff --git a/vm/src/builtins/function.rs b/vm/src/builtins/function.rs index 7f6412477d..d8ceaaeafe 100644 --- a/vm/src/builtins/function.rs +++ b/vm/src/builtins/function.rs @@ -10,6 +10,7 @@ use crate::{ common::lock::PyMutex, frame::Frame, function::{FuncArgs, OptionalArg}, + protocol::PyMapping, scope::Scope, types::{Callable, Comparable, Constructor, GetAttr, GetDescriptor, PyComparisonOp}, IdProtocol, ItemProtocol, PyClassImpl, PyComparisonValue, PyContext, PyObjectRef, PyRef, @@ -267,7 +268,7 @@ impl PyFunction { pub fn invoke_with_locals( &self, func_args: FuncArgs, - locals: Option, + locals: Option, vm: &VirtualMachine, ) -> PyResult { #[cfg(feature = "jit")] @@ -287,11 +288,12 @@ impl PyFunction { let code = &self.code; let locals = if self.code.flags.contains(bytecode::CodeFlags::NEW_LOCALS) { - vm.ctx.new_dict() + PyMapping::new(vm.ctx.new_dict().into()) + } else if let Some(locals) = locals { + locals } else { - locals.unwrap_or_else(|| self.globals.clone()) - } - .into(); + PyMapping::new(self.globals.clone().into()) + }; // Construct frame: let frame = Frame::new( diff --git a/vm/src/frame.rs b/vm/src/frame.rs index a35933fbf3..3f7ce5de79 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -11,12 +11,12 @@ use crate::{ coroutine::Coro, exceptions::ExceptionCtor, function::{FuncArgs, IntoPyResult}, - protocol::{PyIter, PyIterReturn}, + protocol::{PyIter, PyIterReturn, PyMapping}, scope::Scope, stdlib::builtins, types::PyComparisonOp, - IdProtocol, ItemProtocol, PyMethod, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, - TypeProtocol, VirtualMachine, + IdProtocol, ItemProtocol, PyMethod, PyObjectRef, PyObjectWrap, PyRef, PyResult, PyValue, + TryFromObject, TypeProtocol, VirtualMachine, }; use indexmap::IndexMap; use itertools::Itertools; @@ -99,7 +99,7 @@ pub struct Frame { pub fastlocals: PyMutex]>>, pub(crate) cells_frees: Box<[PyCellRef]>, - pub locals: PyObjectRef, + pub locals: PyMapping, pub globals: PyDictRef, pub builtins: PyDictRef, @@ -179,7 +179,7 @@ impl FrameRef { f(exec) } - pub fn locals(&self, vm: &VirtualMachine) -> PyResult { + pub fn locals(&self, vm: &VirtualMachine) -> PyResult { let locals = &self.locals; let code = &**self.code; let map = &code.varnames; @@ -188,9 +188,16 @@ impl FrameRef { let fastlocals = self.fastlocals.lock(); for (k, v) in itertools::zip(&map[..j], &**fastlocals) { if let Some(v) = v { - locals.set_item(k.clone(), v.clone(), vm)?; + match locals.as_object().clone().downcast_exact::(vm) { + Ok(d) => d.set_item(k.clone(), v.clone(), vm)?, + Err(o) => o.set_item(k.clone(), v.clone(), vm)?, + }; } else { - match locals.del_item(k.clone(), vm) { + let res = match locals.as_object().clone().downcast_exact::(vm) { + Ok(d) => d.del_item(k.clone(), vm), + Err(o) => o.del_item(k.clone(), vm), + }; + match res { Ok(()) => {} Err(e) if e.isinstance(&vm.ctx.exceptions.key_error) => {} Err(e) => return Err(e), @@ -202,9 +209,16 @@ impl FrameRef { let map_to_dict = |keys: &[PyStrRef], values: &[PyCellRef]| { for (k, v) in itertools::zip(keys, values) { if let Some(v) = v.get() { - locals.set_item(k.clone(), v, vm)?; + match locals.as_object().clone().downcast_exact::(vm) { + Ok(d) => d.set_item(k.clone(), v, vm)?, + Err(o) => o.set_item(k.clone(), v, vm)?, + }; } else { - match locals.del_item(k.clone(), vm) { + let res = match locals.as_object().clone().downcast_exact::(vm) { + Ok(d) => d.del_item(k.clone(), vm), + Err(o) => o.del_item(k.clone(), vm), + }; + match res { Ok(()) => {} Err(e) if e.isinstance(&vm.ctx.exceptions.key_error) => {} Err(e) => return Err(e), @@ -275,7 +289,7 @@ struct ExecutingFrame<'a> { code: &'a PyRef, fastlocals: &'a PyMutex]>>, cells_frees: &'a [PyCellRef], - locals: &'a PyObjectRef, + locals: &'a PyMapping, globals: &'a PyDictRef, builtins: &'a PyDictRef, object: &'a FrameRef, @@ -497,7 +511,12 @@ impl ExecutingFrame<'_> { bytecode::Instruction::LoadNameAny(idx) => { let name = &self.code.names[*idx as usize]; // Try using locals as dict first, if not, fallback to generic method. - let x = match self.locals.clone().downcast_exact::(vm) { + let x = match self + .locals + .clone() + .into_object() + .downcast_exact::(vm) + { Ok(d) => d.get_item_option(name.clone(), vm)?, Err(o) => o.get_item(name.clone(), vm).ok(), }; @@ -525,7 +544,12 @@ impl ExecutingFrame<'_> { let i = *i as usize; let name = self.code.freevars[i - self.code.cellvars.len()].clone(); // Try using locals as dict first, if not, fallback to generic method. - let value = match self.locals.clone().downcast_exact::(vm) { + let value = match self + .locals + .clone() + .into_object() + .downcast_exact::(vm) + { Ok(d) => d.get_item_option(name, vm)?, Err(o) => o.get_item(name, vm).ok(), }; @@ -544,8 +568,15 @@ impl ExecutingFrame<'_> { } bytecode::Instruction::StoreLocal(idx) => { let value = self.pop_value(); - self.locals - .set_item(self.code.names[*idx as usize].clone(), value, vm)?; + match self + .locals + .clone() + .into_object() + .downcast_exact::(vm) + { + Ok(d) => d.set_item(self.code.names[*idx as usize].clone(), value, vm)?, + Err(o) => o.set_item(self.code.names[*idx as usize].clone(), value, vm)?, + }; Ok(None) } bytecode::Instruction::StoreGlobal(idx) => { @@ -565,7 +596,17 @@ impl ExecutingFrame<'_> { } bytecode::Instruction::DeleteLocal(idx) => { let name = &self.code.names[*idx as usize]; - match self.locals.del_item(name.clone(), vm) { + let res = match self + .locals + .clone() + .into_object() + .downcast_exact::(vm) + { + Ok(d) => d.del_item(name.clone(), vm), + Err(o) => o.del_item(name.clone(), vm), + }; + + match res { Ok(()) => {} Err(e) if e.isinstance(&vm.ctx.exceptions.key_error) => { return Err(vm.new_name_error(format!("name '{}' is not defined", name))) @@ -724,7 +765,12 @@ impl ExecutingFrame<'_> { bytecode::Instruction::YieldFrom => self.execute_yield_from(vm), bytecode::Instruction::SetupAnnotation => { // Try using locals as dict first, if not, fallback to generic method. - let has_annotations = match self.locals.clone().downcast_exact::(vm) { + let has_annotations = match self + .locals + .clone() + .into_object() + .downcast_exact::(vm) + { Ok(d) => d.contains_key("__annotations__", vm), Err(o) => { let needle = vm.new_pyobj("__annotations__"); @@ -732,8 +778,11 @@ impl ExecutingFrame<'_> { } }; if !has_annotations { - self.locals - .set_item("__annotations__", vm.ctx.new_dict().into(), vm)?; + self.locals.as_object().set_item( + "__annotations__", + vm.ctx.new_dict().into(), + vm, + )?; } Ok(None) } @@ -1146,7 +1195,15 @@ impl ExecutingFrame<'_> { for (k, v) in &dict { let k = PyStrRef::try_from_object(vm, k)?; if filter_pred(k.as_str()) { - self.locals.set_item(k, v, vm)?; + match self + .locals + .clone() + .into_object() + .downcast_exact::(vm) + { + Ok(d) => d.set_item(k, v, vm)?, + Err(o) => o.set_item(k, v, vm)?, + }; } } } diff --git a/vm/src/scope.rs b/vm/src/scope.rs index b67f083773..66fa8fd826 100644 --- a/vm/src/scope.rs +++ b/vm/src/scope.rs @@ -1,13 +1,14 @@ use crate::{ builtins::{pystr::IntoPyStrRef, PyDictRef, PyStrRef}, function::IntoPyObject, - ItemProtocol, PyObjectRef, VirtualMachine, + protocol::PyMapping, + ItemProtocol, VirtualMachine, }; use std::fmt; #[derive(Clone)] pub struct Scope { - pub locals: PyObjectRef, + pub locals: PyMapping, pub globals: PyDictRef, } @@ -20,13 +21,13 @@ impl fmt::Debug for Scope { impl Scope { #[inline] - pub fn new(locals: Option, globals: PyDictRef) -> Scope { - let locals = locals.unwrap_or_else(|| globals.clone().into()); + pub fn new(locals: Option, globals: PyDictRef) -> Scope { + let locals = locals.unwrap_or_else(|| PyMapping::new(globals.clone().into())); Scope { locals, globals } } pub fn with_builtins( - locals: Option, + locals: Option, globals: PyDictRef, vm: &VirtualMachine, ) -> Scope { diff --git a/vm/src/stdlib/builtins.rs b/vm/src/stdlib/builtins.rs index b636fdb208..120bff1d32 100644 --- a/vm/src/stdlib/builtins.rs +++ b/vm/src/stdlib/builtins.rs @@ -32,8 +32,8 @@ mod builtins { stdlib::sys, types::PyComparisonOp, utils::Either, - IdProtocol, ItemProtocol, PyArithmeticValue, PyClassImpl, PyObjectRef, PyRef, PyResult, - PyValue, TryFromObject, TypeProtocol, VirtualMachine, + IdProtocol, ItemProtocol, PyArithmeticValue, PyClassImpl, PyObjectRef, PyObjectWrap, PyRef, + PyResult, PyValue, TryFromObject, TypeProtocol, VirtualMachine, }; use num_traits::{Signed, ToPrimitive, Zero}; @@ -204,37 +204,33 @@ mod builtins { struct ScopeArgs { #[pyarg(any, default)] globals: Option, - // TODO: support any mapping for `locals` #[pyarg(any, default)] - locals: Option, + locals: Option, } #[cfg(feature = "rustpython-compiler")] impl ScopeArgs { fn make_scope(self, vm: &VirtualMachine) -> PyResult { - // TODO: Other places where user supplies locals object? - let locals = self.locals; - let get_locals = |default| { - locals.map_or(Ok(default), |locals| { - if !PyMapping::check(&locals, vm) { - Err(vm.new_type_error("locals must be a mapping".to_owned())) - } else { - Ok(locals) - } - }) - }; let (globals, locals) = match self.globals { Some(globals) => { if !globals.contains_key("__builtins__", vm) { let builtins_dict = vm.builtins.dict().unwrap().into(); globals.set_item("__builtins__", builtins_dict, vm)?; } - (globals.clone(), get_locals(globals.into())?) - } - None => { - let globals = vm.current_globals().clone(); - (globals, vm.current_locals().and_then(get_locals)?) + ( + globals.clone(), + self.locals + .unwrap_or_else(|| PyMapping::new(globals.into())), + ) } + None => ( + vm.current_globals().clone(), + if let Some(locals) = self.locals { + locals + } else { + vm.current_locals()? + }, + ), }; let scope = Scope::with_builtins(Some(locals), globals, vm); @@ -432,7 +428,7 @@ mod builtins { } #[pyfunction] - fn locals(vm: &VirtualMachine) -> PyResult { + fn locals(vm: &VirtualMachine) -> PyResult { vm.current_locals() } @@ -799,7 +795,7 @@ mod builtins { vm.new_type_error("vars() argument must have __dict__ attribute".to_owned()) }) } else { - Ok(vm.current_locals()?) + Ok(vm.current_locals()?.into_object()) } } @@ -876,13 +872,22 @@ mod builtins { FuncArgs::new(vec![name_obj.clone().into(), bases.clone()], kwargs.clone()), )?; - let namespace = PyDictRef::try_from_object(vm, namespace)?; + // Accept any PyMapping as namespace. + let namespace = PyMapping::try_from_object(vm, namespace.clone()).map_err(|_| { + vm.new_type_error(format!( + "{}.__prepare__() must return a mapping, not {}", + metaclass.name(), + namespace.class().name() + )) + })?; let classcell = function.invoke_with_locals(().into(), Some(namespace.clone()), vm)?; let classcell = >::try_from_object(vm, classcell)?; if let Some(orig_bases) = orig_bases { - namespace.set_item("__orig_bases__", orig_bases.into(), vm)?; + namespace + .as_object() + .set_item("__orig_bases__", orig_bases.into(), vm)?; } let class = vm.invoke( diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 2f7d82c0ac..5299dc560a 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -22,14 +22,14 @@ use crate::{ frozen, function::{FuncArgs, IntoFuncArgs, IntoPyObject}, import, - protocol::{PyIterIter, PyIterReturn}, + protocol::{PyIterIter, PyIterReturn, PyMapping}, scope::Scope, signal::NSIG, stdlib, types::PyComparisonOp, utils::Either, IdProtocol, ItemProtocol, PyArithmeticValue, PyContext, PyLease, PyMethod, PyObject, - PyObjectRef, PyRef, PyRefExact, PyResult, PyValue, TryFromObject, TypeProtocol, + PyObjectRef, PyObjectWrap, PyRef, PyRefExact, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crossbeam_utils::atomic::AtomicCell; use num_traits::{Signed, ToPrimitive}; @@ -558,7 +558,7 @@ impl VirtualMachine { } } - pub fn current_locals(&self) -> PyResult { + pub fn current_locals(&self) -> PyResult { self.current_frame() .expect("called current_locals but no frames on the stack") .locals(self) @@ -1201,7 +1201,7 @@ impl VirtualMachine { .get_special_method(obj, "__dir__")? .map_err(|_obj| self.new_type_error("object does not provide __dir__".to_owned()))? .invoke((), self)?, - None => self.call_method(&self.current_locals()?, "keys", ())?, + None => self.call_method(self.current_locals()?.as_object(), "keys", ())?, }; let items = self.extract_elements(&seq)?; let lst = PyList::from(items);