Skip to content

Commit dce4582

Browse files
committed
Allow any mapping for locals.
1 parent 8df1c57 commit dce4582

File tree

8 files changed

+59
-46
lines changed

8 files changed

+59
-46
lines changed

Lib/test/test_builtin.py

-2
Original file line numberDiff line numberDiff line change
@@ -490,8 +490,6 @@ def __getitem__(self, key):
490490
raise ValueError
491491
self.assertRaises(ValueError, eval, "foo", {}, X())
492492

493-
# TODO: RUSTPYTHON
494-
@unittest.expectedFailure
495493
def test_general_eval(self):
496494
# Tests that general mappings can be used for the locals argument
497495

Lib/test/test_compile.py

-2
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ def test_none_keyword_arg(self):
6161
def test_duplicate_global_local(self):
6262
self.assertRaises(SyntaxError, exec, 'def f(a): global a; a = 1')
6363

64-
# TODO: RUSTPYTHON
65-
@unittest.expectedFailure
6664
def test_exec_with_general_mapping_for_locals(self):
6765

6866
class M:

vm/src/builtins/frame.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ impl FrameRef {
4444
}
4545

4646
#[pyproperty]
47-
fn f_locals(self, vm: &VirtualMachine) -> PyResult<PyDictRef> {
47+
fn f_locals(self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
4848
self.locals(vm)
4949
}
5050

vm/src/builtins/function.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,8 @@ impl PyFunction {
290290
vm.ctx.new_dict()
291291
} else {
292292
locals.unwrap_or_else(|| self.globals.clone())
293-
};
293+
}
294+
.into();
294295

295296
// Construct frame:
296297
let frame = Frame::new(

vm/src/frame.rs

+32-22
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ pub struct Frame {
9999

100100
pub fastlocals: PyMutex<Box<[Option<PyObjectRef>]>>,
101101
pub(crate) cells_frees: Box<[PyCellRef]>,
102-
pub locals: PyDictRef,
102+
pub locals: PyObjectRef,
103103
pub globals: PyDictRef,
104104
pub builtins: PyDictRef,
105105

@@ -179,7 +179,7 @@ impl FrameRef {
179179
f(exec)
180180
}
181181

182-
pub fn locals(&self, vm: &VirtualMachine) -> PyResult<PyDictRef> {
182+
pub fn locals(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
183183
let locals = &self.locals;
184184
let code = &**self.code;
185185
let map = &code.varnames;
@@ -275,7 +275,7 @@ struct ExecutingFrame<'a> {
275275
code: &'a PyRef<PyCode>,
276276
fastlocals: &'a PyMutex<Box<[Option<PyObjectRef>]>>,
277277
cells_frees: &'a [PyCellRef],
278-
locals: &'a PyDictRef,
278+
locals: &'a PyObjectRef,
279279
globals: &'a PyDictRef,
280280
builtins: &'a PyDictRef,
281281
object: &'a FrameRef,
@@ -496,12 +496,15 @@ impl ExecutingFrame<'_> {
496496
}
497497
bytecode::Instruction::LoadNameAny(idx) => {
498498
let name = &self.code.names[*idx as usize];
499-
let x = self.locals.get_item_option(name.clone(), vm)?;
500-
let x = match x {
499+
// Try using locals as dict first, if not, fallback to generic method.
500+
let x = match self.locals.clone().downcast_exact::<PyDict>(vm) {
501+
Ok(d) => d.get_item_option(name.clone(), vm)?,
502+
Err(o) => o.get_item(name.clone(), vm).ok(),
503+
};
504+
self.push_value(match x {
501505
Some(x) => x,
502506
None => self.load_global_or_builtin(name, vm)?,
503-
};
504-
self.push_value(x);
507+
});
505508
Ok(None)
506509
}
507510
bytecode::Instruction::LoadGlobal(idx) => {
@@ -521,14 +524,17 @@ impl ExecutingFrame<'_> {
521524
bytecode::Instruction::LoadClassDeref(i) => {
522525
let i = *i as usize;
523526
let name = self.code.freevars[i - self.code.cellvars.len()].clone();
524-
let value = if let Some(value) = self.locals.get_item_option(name, vm)? {
525-
value
526-
} else {
527-
self.cells_frees[i]
528-
.get()
529-
.ok_or_else(|| self.unbound_cell_exception(i, vm))?
527+
// Try using locals as dict first, if not, fallback to generic method.
528+
let value = match self.locals.clone().downcast_exact::<PyDict>(vm) {
529+
Ok(d) => d.get_item_option(name, vm)?,
530+
Err(o) => o.get_item(name, vm).ok(),
530531
};
531-
self.push_value(value);
532+
self.push_value(match value {
533+
Some(v) => v,
534+
None => self.cells_frees[i]
535+
.get()
536+
.ok_or_else(|| self.unbound_cell_exception(i, vm))?,
537+
});
532538
Ok(None)
533539
}
534540
bytecode::Instruction::StoreFast(idx) => {
@@ -717,7 +723,15 @@ impl ExecutingFrame<'_> {
717723
}
718724
bytecode::Instruction::YieldFrom => self.execute_yield_from(vm),
719725
bytecode::Instruction::SetupAnnotation => {
720-
if !self.locals.contains_key("__annotations__", vm) {
726+
// Try using locals as dict first, if not, fallback to generic method.
727+
let has_annotations = match self.locals.clone().downcast_exact::<PyDict>(vm) {
728+
Ok(d) => d.contains_key("__annotations__", vm),
729+
Err(o) => {
730+
let needle = vm.new_pyobj("__annotations__");
731+
self._in(vm, needle, o)?
732+
}
733+
};
734+
if !has_annotations {
721735
self.locals
722736
.set_item("__annotations__", vm.ctx.new_dict().into(), vm)?;
723737
}
@@ -1822,15 +1836,11 @@ impl fmt::Debug for Frame {
18221836
.map(|elem| format!("\n > {:?}", elem))
18231837
.collect::<String>();
18241838
// TODO: fix this up
1825-
let dict = self.locals.clone();
1826-
let local_str = dict
1827-
.into_iter()
1828-
.map(|elem| format!("\n {:?} = {:?}", elem.0, elem.1))
1829-
.collect::<String>();
1839+
let locals = self.locals.clone();
18301840
write!(
18311841
f,
1832-
"Frame Object {{ \n Stack:{}\n Blocks:{}\n Locals:{}\n}}",
1833-
stack_str, block_str, local_str
1842+
"Frame Object {{ \n Stack:{}\n Blocks:{}\n Locals:{:?}\n}}",
1843+
stack_str, block_str, locals
18341844
)
18351845
}
18361846
}

vm/src/scope.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use crate::{
22
builtins::{pystr::IntoPyStrRef, PyDictRef, PyStrRef},
33
function::IntoPyObject,
4-
ItemProtocol, VirtualMachine,
4+
ItemProtocol, PyObjectRef, VirtualMachine,
55
};
66
use std::fmt;
77

88
#[derive(Clone)]
99
pub struct Scope {
10-
pub locals: PyDictRef,
10+
pub locals: PyObjectRef,
1111
pub globals: PyDictRef,
1212
}
1313

@@ -20,13 +20,13 @@ impl fmt::Debug for Scope {
2020

2121
impl Scope {
2222
#[inline]
23-
pub fn new(locals: Option<PyDictRef>, globals: PyDictRef) -> Scope {
24-
let locals = locals.unwrap_or_else(|| globals.clone());
23+
pub fn new(locals: Option<PyObjectRef>, globals: PyDictRef) -> Scope {
24+
let locals = locals.unwrap_or_else(|| globals.clone().into());
2525
Scope { locals, globals }
2626
}
2727

2828
pub fn with_builtins(
29-
locals: Option<PyDictRef>,
29+
locals: Option<PyObjectRef>,
3030
globals: PyDictRef,
3131
vm: &VirtualMachine,
3232
) -> Scope {

vm/src/stdlib/builtins.rs

+17-11
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ mod builtins {
2525
ArgBytesLike, ArgCallable, ArgIntoBool, ArgIterable, FuncArgs, KwArgs, OptionalArg,
2626
OptionalOption, PosArgs,
2727
},
28-
protocol::{PyIter, PyIterReturn},
28+
protocol::{PyIter, PyIterReturn, PyMapping},
2929
py_io,
3030
readline::{Readline, ReadlineResult},
3131
scope::Scope,
@@ -206,28 +206,34 @@ mod builtins {
206206
globals: Option<PyDictRef>,
207207
// TODO: support any mapping for `locals`
208208
#[pyarg(any, default)]
209-
locals: Option<PyDictRef>,
209+
locals: Option<PyObjectRef>,
210210
}
211211

212212
#[cfg(feature = "rustpython-compiler")]
213213
impl ScopeArgs {
214214
fn make_scope(self, vm: &VirtualMachine) -> PyResult<Scope> {
215+
// TODO: Other places where user supplies locals object?
216+
let locals = self.locals;
217+
let get_locals = |default| {
218+
locals.map_or(Ok(default), |locals| {
219+
if !PyMapping::check(&locals, vm) {
220+
Err(vm.new_type_error("locals must be a mapping".to_owned()))
221+
} else {
222+
Ok(locals)
223+
}
224+
})
225+
};
215226
let (globals, locals) = match self.globals {
216227
Some(globals) => {
217228
if !globals.contains_key("__builtins__", vm) {
218229
let builtins_dict = vm.builtins.dict().unwrap().into();
219230
globals.set_item("__builtins__", builtins_dict, vm)?;
220231
}
221-
let locals = self.locals.unwrap_or_else(|| globals.clone());
222-
(globals, locals)
232+
(globals.clone(), get_locals(globals.into())?)
223233
}
224234
None => {
225235
let globals = vm.current_globals().clone();
226-
let locals = match self.locals {
227-
Some(l) => l,
228-
None => vm.current_locals()?,
229-
};
230-
(globals, locals)
236+
(globals, vm.current_locals().and_then(get_locals)?)
231237
}
232238
};
233239

@@ -426,7 +432,7 @@ mod builtins {
426432
}
427433

428434
#[pyfunction]
429-
fn locals(vm: &VirtualMachine) -> PyResult<PyDictRef> {
435+
fn locals(vm: &VirtualMachine) -> PyResult<PyObjectRef> {
430436
vm.current_locals()
431437
}
432438

@@ -793,7 +799,7 @@ mod builtins {
793799
vm.new_type_error("vars() argument must have __dict__ attribute".to_owned())
794800
})
795801
} else {
796-
Ok(vm.current_locals()?.into())
802+
Ok(vm.current_locals()?)
797803
}
798804
}
799805

vm/src/vm.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ impl VirtualMachine {
558558
}
559559
}
560560

561-
pub fn current_locals(&self) -> PyResult<PyDictRef> {
561+
pub fn current_locals(&self) -> PyResult<PyObjectRef> {
562562
self.current_frame()
563563
.expect("called current_locals but no frames on the stack")
564564
.locals(self)
@@ -1201,7 +1201,7 @@ impl VirtualMachine {
12011201
.get_special_method(obj, "__dir__")?
12021202
.map_err(|_obj| self.new_type_error("object does not provide __dir__".to_owned()))?
12031203
.invoke((), self)?,
1204-
None => self.call_method(self.current_locals()?.as_object(), "keys", ())?,
1204+
None => self.call_method(&self.current_locals()?, "keys", ())?,
12051205
};
12061206
let items = self.extract_elements(&seq)?;
12071207
let lst = PyList::from(items);

0 commit comments

Comments
 (0)