Fast locals part 1

This commit is contained in:
Noah committed 2020-12-05 16:36:00 -06:00
1 parent 479610f3cb
commit 97029af713
14 files changed
+703 -523

No files matched your search

+79 -48
View File
@@ -108,6 +108,9 @@ pub struct CodeObject<C: Constant = ConstantData> {
serialize = "C::Name: serde::Serialize"
))]
pub names: Vec<C::Name>,
pub varnames: Vec<C::Name>,
pub cellvars: Vec<C::Name>,
pub freevars: Vec<C::Name>,
}
bitflags! {
@@ -121,6 +124,7 @@ bitflags! {
const IS_COROUTINE = 0x20;
const HAS_VARARGS = 0x40;
const HAS_VARKEYWORDS = 0x80;
const IS_OPTIMIZED = 0x0100;
}
}
@@ -154,22 +158,6 @@ impl fmt::Display for Label {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
/// An indication where the name must be accessed.
pub enum NameScope {
/// The name will be in the local scope.
Local,
/// The name will be located in scope surrounding the current scope.
NonLocal,
/// The name will be in global scope.
Global,
/// The name will be located in any scope between the current scope and the top scope.
Free,
}
/// Transforms a value prior to formatting it.
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ConversionFlag {
@@ -195,17 +183,20 @@ pub enum Instruction {
ImportFrom {
idx: NameIdx,
},
LoadName {
idx: NameIdx,
scope: NameScope,
},
StoreName {
idx: NameIdx,
scope: NameScope,
},
DeleteName {
idx: NameIdx,
},
LoadFast(NameIdx),
LoadLocal(NameIdx),
LoadGlobal(NameIdx),
LoadDeref(NameIdx),
LoadClassDeref(NameIdx),
StoreFast(NameIdx),
StoreLocal(NameIdx),
StoreGlobal(NameIdx),
StoreDeref(NameIdx),
DeleteFast(NameIdx),
DeleteLocal(NameIdx),
DeleteGlobal(NameIdx),
DeleteDeref(NameIdx),
LoadClosure(NameIdx),
Subscript,
StoreSubscript,
DeleteSubscript,
@@ -561,6 +552,9 @@ impl<C: Constant> CodeObject<C> {
obj_name,
constants: Vec::new(),
names: Vec::new(),
varnames: Vec::new(),
cellvars: Vec::new(),
freevars: Vec::new(),
}
}
@@ -569,19 +563,19 @@ impl<C: Constant> CodeObject<C> {
let nargs = self.arg_count;
let nkwargs = self.kwonlyarg_count;
let mut varargspos = nargs + nkwargs;
let posonlyargs = &self.names[..self.posonlyarg_count];
let args = &self.names[..nargs];
let kwonlyargs = &self.names[nargs..varargspos];
let posonlyargs = &self.varnames[..self.posonlyarg_count];
let args = &self.varnames[..nargs];
let kwonlyargs = &self.varnames[nargs..varargspos];
let vararg = if self.flags.contains(CodeFlags::HAS_VARARGS) {
let vararg = &self.names[varargspos];
let vararg = &self.varnames[varargspos];
varargspos += 1;
Some(vararg)
} else {
None
};
let varkwarg = if self.flags.contains(CodeFlags::HAS_VARKEYWORDS) {
Some(&self.names[varargspos])
Some(&self.varnames[varargspos])
} else {
None
};
@@ -617,8 +611,10 @@ impl<C: Constant> CodeObject<C> {
}
#[rustfmt::skip]
Import { .. } | ImportStar | ImportFrom { .. } | LoadName { .. } | StoreName { .. }
| DeleteName { .. } | Subscript | StoreSubscript | DeleteSubscript
Import { .. } | ImportStar | ImportFrom { .. } | LoadFast(_) | LoadLocal(_)
| LoadGlobal(_) | LoadDeref(_) | LoadClassDeref(_) | StoreFast(_) | StoreLocal(_)
| StoreGlobal(_) | StoreDeref(_) | DeleteFast(_) | DeleteLocal(_) | DeleteGlobal(_)
| DeleteDeref(_) | LoadClosure(_) | Subscript | StoreSubscript | DeleteSubscript
| StoreAttr { .. } | DeleteAttr { .. } | LoadConst { .. } | UnaryOperation { .. }
| BinaryOperation { .. } | LoadAttr { .. } | CompareOperation { .. } | Pop
| Rotate { .. } | Duplicate | GetIter | Continue | Break | MakeFunction
@@ -652,7 +648,16 @@ impl<C: Constant> CodeObject<C> {
write!(f, " ")?;
}
write!(f, "{} {:5} ", arrow, offset)?;
instruction.fmt_dis(f, &self.constants, &self.names, expand_codeobjects, level)?;
instruction.fmt_dis(
f,
&self.constants,
&self.names,
&self.varnames,
&self.cellvars,
&self.freevars,
expand_codeobjects,
level,
)?;
}
Ok(())
}
@@ -668,17 +673,22 @@ impl<C: Constant> CodeObject<C> {
}
pub fn map_bag<Bag: ConstantBag>(self, bag: &Bag) -> CodeObject<Bag::Constant> {
let map_names = |names: Vec<C::Name>| {
names
.into_iter()
.map(|x| bag.make_name_ref(x.as_ref()))
.collect::<Vec<_>>()
};
CodeObject {
constants: self
.constants
.into_iter()
.map(|x| x.map_constant(bag))
.collect(),
names: self
.names
.into_iter()
.map(|x| bag.make_name_ref(x.as_ref()))
.collect(),
names: map_names(self.names),
varnames: map_names(self.varnames),
cellvars: map_names(self.cellvars),
freevars: map_names(self.freevars),
instructions: self.instructions,
locations: self.locations,
@@ -693,17 +703,22 @@ impl<C: Constant> CodeObject<C> {
}
pub fn map_clone_bag<Bag: ConstantBag>(&self, bag: &Bag) -> CodeObject<Bag::Constant> {
let map_names = |names: &[C::Name]| {
names
.iter()
.map(|x| bag.make_name_ref(x.as_ref()))
.collect()
};
CodeObject {
constants: self
.constants
.iter()
.map(|x| bag.make_constant_borrowed(x.borrow_constant()))
.collect(),
names: self
.names
.iter()
.map(|x| bag.make_name_ref(x.as_ref()))
.collect(),
names: map_names(&self.names),
varnames: map_names(&self.varnames),
cellvars: map_names(&self.cellvars),
freevars: map_names(&self.freevars),
instructions: self.instructions.clone(),
locations: self.locations.clone(),
@@ -755,6 +770,9 @@ impl Instruction {
f: &mut fmt::Formatter,
constants: &[C],
names: &[C::Name],
varnames: &[C::Name],
cellvars: &[C::Name],
freevars: &[C::Name],
expand_codeobjects: bool,
level: usize,
) -> fmt::Result {
@@ -780,6 +798,8 @@ impl Instruction {
};
}
let cellname = |i: usize| cellvars.get(i).unwrap_or_else(|| &freevars[i]).as_ref();
match self {
Import {
name_idx,
@@ -799,9 +819,20 @@ impl Instruction {
),
ImportStar => w!(ImportStar),
ImportFrom { idx } => w!(ImportFrom, names[*idx].as_ref()),
LoadName { idx, scope } => w!(LoadName, names[*idx].as_ref(), format!("{:?}", scope)),
StoreName { idx, scope } => w!(StoreName, names[*idx].as_ref(), format!("{:?}", scope)),
DeleteName { idx } => w!(DeleteName, names[*idx].as_ref()),
LoadFast(idx) => w!(LoadFast, varnames[*idx].as_ref()),
LoadLocal(idx) => w!(LoadLocal, names[*idx].as_ref()),
LoadGlobal(idx) => w!(LoadGlobal, names[*idx].as_ref()),
LoadDeref(idx) => w!(LoadDeref, cellname(*idx)),
LoadClassDeref(idx) => w!(LoadClassDeref, cellname(*idx)),
StoreFast(idx) => w!(StoreFast, varnames[*idx].as_ref()),
StoreLocal(idx) => w!(StoreLocal, names[*idx].as_ref()),
StoreGlobal(idx) => w!(StoreGlobal, names[*idx].as_ref()),
StoreDeref(idx) => w!(StoreDeref, cellname(*idx)),
DeleteFast(idx) => w!(DeleteFast, varnames[*idx].as_ref()),
DeleteLocal(idx) => w!(DeleteLocal, names[*idx].as_ref()),
DeleteGlobal(idx) => w!(DeleteGlobal, names[*idx].as_ref()),
DeleteDeref(idx) => w!(DeleteDeref, cellname(*idx)),
LoadClosure(i) => w!(LoadClosure, cellname(*i)),
Subscript => w!(Subscript),
StoreSubscript => w!(StoreSubscript),
DeleteSubscript => w!(DeleteSubscript),
+201 -146
View File
@@ -7,9 +7,7 @@
use crate::error::{CompileError, CompileErrorType};
pub use crate::mode::Mode;
use crate::symboltable::{
make_symbol_table, statements_to_symbol_table, Symbol, SymbolScope, SymbolTable,
};
use crate::symboltable::{make_symbol_table, statements_to_symbol_table, SymbolScope, SymbolTable};
use indexmap::IndexSet;
use itertools::Itertools;
use num_complex::Complex64;
@@ -21,6 +19,9 @@ type CompileResult<T> = Result<T, CompileError>;
struct CodeInfo {
code: CodeObject,
name_cache: IndexSet<String>,
varname_cache: IndexSet<String>,
cellvar_cache: IndexSet<String>,
freevar_cache: IndexSet<String>,
label_map: Vec<Option<Label>>,
}
impl CodeInfo {
@@ -28,9 +29,17 @@ impl CodeInfo {
let CodeInfo {
mut code,
name_cache,
varname_cache,
cellvar_cache,
freevar_cache,
label_map,
} = self;
code.names.extend(name_cache);
code.varnames.extend(varname_cache);
code.cellvars.extend(cellvar_cache);
code.freevars.extend(freevar_cache);
for instruction in &mut code.instructions {
use Instruction::*;
// this is a little bit hacky, as until now the data stored inside Labels in
@@ -55,8 +64,10 @@ impl CodeInfo {
}
#[rustfmt::skip]
Import { .. } | ImportStar | ImportFrom { .. } | LoadName { .. } | StoreName { .. }
| DeleteName { .. } | Subscript | StoreSubscript | DeleteSubscript
Import { .. } | ImportStar | ImportFrom { .. } | LoadFast(_) | LoadLocal(_)
| LoadGlobal(_) | LoadDeref(_) | LoadClassDeref(_) | StoreFast(_) | StoreLocal(_)
| StoreGlobal(_) | StoreDeref(_) | DeleteFast(_) | DeleteLocal(_) | DeleteGlobal(_)
| DeleteDeref(_) | LoadClosure(_) | Subscript | StoreSubscript | DeleteSubscript
| StoreAttr { .. } | DeleteAttr { .. } | LoadConst { .. } | UnaryOperation { .. }
| BinaryOperation { .. } | LoadAttr { .. } | CompareOperation { .. } | Pop
| Rotate { .. } | Duplicate | GetIter | Continue | Break | MakeFunction
@@ -73,6 +84,12 @@ impl CodeInfo {
}
}
enum NameUsage {
Load,
Store,
Delete,
}
/// Main structure holding the state of compilation.
struct Compiler {
code_stack: Vec<CodeInfo>,
@@ -100,6 +117,7 @@ impl Default for CompileOpts {
#[derive(Clone, Copy)]
struct CompileContext {
in_loop: bool,
in_class: bool,
func: FunctionContext,
}
@@ -122,8 +140,7 @@ fn with_compiler(
opts: CompileOpts,
f: impl FnOnce(&mut Compiler) -> CompileResult<()>,
) -> CompileResult<CodeObject> {
let mut compiler = Compiler::new(opts, source_path);
compiler.push_new_code_object("<module>".to_owned());
let mut compiler = Compiler::new(opts, source_path, "<module>".to_owned());
f(&mut compiler)?;
let code = compiler.pop_code_object();
trace!("Compilation completed: {:?}", code);
@@ -176,9 +193,25 @@ pub fn compile_program_single(
}
impl Compiler {
fn new(opts: CompileOpts, source_path: String) -> Self {
fn new(opts: CompileOpts, source_path: String, code_name: String) -> Self {
let module_code = CodeInfo {
code: CodeObject::new(
Default::default(),
0,
0,
0,
source_path.clone(),
0,
code_name,
),
name_cache: IndexSet::new(),
varname_cache: IndexSet::new(),
cellvar_cache: IndexSet::new(),
freevar_cache: IndexSet::new(),
label_map: Vec::new(),
};
Compiler {
code_stack: Vec::new(),
code_stack: vec![module_code],
symbol_table_stack: Vec::new(),
source_path,
current_source_location: ast::Location::default(),
@@ -186,6 +219,7 @@ impl Compiler {
done_with_future_stmts: false,
ctx: CompileContext {
in_loop: false,
in_class: false,
func: FunctionContext::NoFunction,
},
opts,
@@ -204,43 +238,63 @@ impl Compiler {
}
fn push_output(&mut self, code: CodeObject) {
self.code_stack.push(CodeInfo {
let table = self
.symbol_table_stack
.last_mut()
.unwrap()
.sub_tables
.remove(0);
let cellvar_cache = table
.symbols
.iter()
.filter(|(_, s)| matches!(s.scope, SymbolScope::Cell))
.map(|(var, _)| var.clone())
.collect();
let freevar_cache = table
.symbols
.iter()
// TODO: check if Free or FREE_CLASS symbol
.filter(|(_, s)| matches!(s.scope, SymbolScope::Free))
.map(|(var, _)| var.clone())
.collect();
self.symbol_table_stack.push(table);
let info = CodeInfo {
code,
name_cache: IndexSet::new(),
varname_cache: IndexSet::new(),
cellvar_cache,
freevar_cache,
label_map: Vec::new(),
});
}
fn push_new_code_object(&mut self, obj_name: String) {
let line_number = self.get_source_line_number();
self.push_output(CodeObject::new(
Default::default(),
0,
0,
0,
self.source_path.clone(),
line_number,
obj_name,
));
};
self.code_stack.push(info);
}
fn pop_code_object(&mut self) -> CodeObject {
let table = self.symbol_table_stack.pop().unwrap();
assert!(table.sub_tables.is_empty());
self.code_stack.pop().unwrap().finalize_code()
}
// could take impl Into<Cow<str>>, but everything is borrowed from ast structs; we never
// actually have a `String` to pass
fn name(&mut self, name: &str) -> bytecode::NameIdx {
let cache = &mut self
.code_stack
.last_mut()
.expect("nothing on stack")
.name_cache;
if let Some(x) = cache.get_index_of(name) {
x
} else {
cache.insert_full(name.to_owned()).0
}
self._name_inner(name, |i| &mut i.name_cache)
}
fn varname(&mut self, name: &str) -> bytecode::NameIdx {
self._name_inner(name, |i| &mut i.varname_cache)
}
fn _name_inner(
&mut self,
name: &str,
cache: impl FnOnce(&mut CodeInfo) -> &mut IndexSet<String>,
) -> bytecode::NameIdx {
let cache = cache(self.code_stack.last_mut().expect("nothing on stack"));
cache
.get_index_of(name)
.unwrap_or_else(|| cache.insert_full(name.to_owned()).0)
}
fn compile_program(
@@ -255,10 +309,7 @@ impl Compiler {
if let Some(value) = doc {
self.emit_constant(bytecode::ConstantData::Str { value });
let doc = self.name("__doc__");
self.emit(Instruction::StoreName {
idx: doc,
scope: bytecode::NameScope::Global,
});
self.emit(Instruction::StoreGlobal(doc))
}
self.compile_statements(statements)?;
@@ -331,34 +382,73 @@ impl Compiler {
Ok(())
}
fn scope_for_name(&self, name: &str) -> bytecode::NameScope {
let symbol = self.lookup_name(name);
match symbol.scope {
SymbolScope::Global => bytecode::NameScope::Global,
SymbolScope::Nonlocal => bytecode::NameScope::NonLocal,
SymbolScope::Unknown => bytecode::NameScope::Free,
SymbolScope::Local => {
// Only in function block, we use load local
// https://github.com/python/cpython/blob/master/Python/compile.c#L3582
if self.ctx.in_func() {
bytecode::NameScope::Local
} else {
bytecode::NameScope::Free
}
}
}
}
fn load_name(&mut self, name: &str) {
let scope = self.scope_for_name(name);
let idx = self.name(name);
self.emit(Instruction::LoadName { idx, scope });
self.compile_name(name, NameUsage::Load)
}
fn store_name(&mut self, name: &str) {
let scope = self.scope_for_name(name);
let idx = self.name(name);
self.emit(Instruction::StoreName { idx, scope });
self.compile_name(name, NameUsage::Store)
}
fn compile_name(&mut self, name: &str, usage: NameUsage) {
let symbol_table = self.symbol_table_stack.last().unwrap();
let symbol = symbol_table.lookup(name).expect(
"The symbol must be present in the symbol table, even when it is undefined in python.",
);
let info = self.code_stack.last_mut().unwrap();
let mut cache = &mut info.name_cache;
enum NameOpType {
Fast,
Global,
Deref,
Local,
}
let op_typ = match symbol.scope {
SymbolScope::Local if self.ctx.in_func() => {
cache = &mut info.varname_cache;
NameOpType::Fast
}
SymbolScope::Local => NameOpType::Local,
SymbolScope::GlobalImplicit if self.ctx.in_func() => NameOpType::Global,
SymbolScope::GlobalImplicit => NameOpType::Local,
SymbolScope::GlobalExplicit => NameOpType::Global,
SymbolScope::Free => {
cache = &mut info.freevar_cache;
NameOpType::Deref
}
SymbolScope::Cell => {
cache = &mut info.cellvar_cache;
NameOpType::Deref
}
// TODO: is this right?
SymbolScope::Unknown => NameOpType::Global,
};
let idx = cache
.get_index_of(name)
.unwrap_or_else(|| cache.insert_full(name.to_owned()).0);
let op = match op_typ {
NameOpType::Fast => match usage {
NameUsage::Load => Instruction::LoadFast,
NameUsage::Store => Instruction::StoreFast,
NameUsage::Delete => Instruction::DeleteFast,
},
NameOpType::Global => match usage {
NameUsage::Load => Instruction::LoadGlobal,
NameUsage::Store => Instruction::StoreGlobal,
NameUsage::Delete => Instruction::DeleteGlobal,
},
NameOpType::Deref => match usage {
NameUsage::Load => Instruction::LoadDeref,
NameUsage::Store => Instruction::StoreDeref,
NameUsage::Delete => Instruction::DeleteDeref,
},
NameOpType::Local => match usage {
NameUsage::Load => Instruction::LoadLocal,
NameUsage::Store => Instruction::StoreLocal,
NameUsage::Delete => Instruction::DeleteLocal,
},
};
self.emit(op(idx));
}
fn compile_statement(&mut self, statement: &ast::Statement) -> CompileResult<()> {
@@ -584,10 +674,7 @@ impl Compiler {
let end_label = self.new_label();
self.compile_jump_if(test, true, end_label)?;
let assertion_error = self.name("AssertionError");
self.emit(Instruction::LoadName {
idx: assertion_error,
scope: bytecode::NameScope::Global,
});
self.emit(Instruction::LoadGlobal(assertion_error));
match msg {
Some(e) => {
self.compile_expression(e)?;
@@ -683,8 +770,7 @@ impl Compiler {
fn compile_delete(&mut self, expression: &ast::Expression) -> CompileResult<()> {
match &expression.node {
ast::ExpressionType::Identifier { name } => {
let idx = self.name(name);
self.emit(Instruction::DeleteName { idx });
self.compile_name(name, NameUsage::Delete);
}
ast::ExpressionType::Attribute { value, name } => {
self.compile_expression(value)?;
@@ -738,7 +824,7 @@ impl Compiler {
});
}
let mut flags = bytecode::CodeFlags::default();
let mut flags = bytecode::CodeFlags::NEW_LOCALS | bytecode::CodeFlags::IS_OPTIMIZED;
if have_defaults {
flags |= bytecode::CodeFlags::HAS_DEFAULTS;
}
@@ -775,8 +861,6 @@ impl Compiler {
compile_varargs(&args.vararg, bytecode::CodeFlags::HAS_VARARGS);
compile_varargs(&args.kwarg, bytecode::CodeFlags::HAS_VARKEYWORDS);
self.enter_scope();
Ok(())
}
@@ -923,6 +1007,7 @@ impl Compiler {
self.ctx = CompileContext {
in_loop: false,
in_class: prev_ctx.in_class,
func: if is_async {
FunctionContext::AsyncFunction
} else {
@@ -954,7 +1039,6 @@ impl Compiler {
}
let mut code = self.pop_code_object();
self.leave_scope();
// Prepare type annotations:
let mut num_annotations = 0;
@@ -1006,6 +1090,27 @@ impl Compiler {
code.flags |= bytecode::CodeFlags::IS_COROUTINE;
}
if !code.freevars.is_empty() {
for var in &code.freevars {
let symbol = self.symbol_table_stack.last().unwrap().lookup(var).unwrap();
let parent_code = self.code_stack.last().unwrap();
let vars = match symbol.scope {
SymbolScope::Free => &parent_code.freevar_cache,
SymbolScope::Cell => &parent_code.cellvar_cache,
_ => unreachable!(),
};
let mut idx = vars.get_index_of(var).unwrap();
if let SymbolScope::Free = symbol.scope {
idx += parent_code.cellvar_cache.len();
}
self.emit(Instruction::LoadClosure(idx))
}
self.emit(Instruction::BuildTuple {
size: code.freevars.len(),
unpack: false,
})
}
self.emit_constant(bytecode::ConstantData::Code {
code: Box::new(code),
});
@@ -1098,6 +1203,7 @@ impl Compiler {
let prev_ctx = self.ctx;
self.ctx = CompileContext {
func: FunctionContext::NoFunction,
in_class: true,
in_loop: false,
};
@@ -1109,7 +1215,7 @@ impl Compiler {
self.emit(Instruction::LoadBuildClass);
let line_number = self.get_source_line_number();
self.push_output(CodeObject::new(
Default::default(),
bytecode::CodeFlags::empty(),
0,
0,
0,
@@ -1117,34 +1223,21 @@ impl Compiler {
line_number,
name.to_owned(),
));
self.enter_scope();
let (new_body, doc_str) = get_doc(body);
let dunder_name = self.name("__name__");
self.emit(Instruction::LoadName {
idx: dunder_name,
scope: bytecode::NameScope::Global,
});
self.emit(Instruction::LoadGlobal(dunder_name));
let dunder_module = self.name("__module__");
self.emit(Instruction::StoreName {
idx: dunder_module,
scope: bytecode::NameScope::Free,
});
self.emit(Instruction::StoreLocal(dunder_module));
self.emit_constant(bytecode::ConstantData::Str {
value: qualified_name.clone(),
});
let qualname = self.name("__qualname__");
self.emit(Instruction::StoreName {
idx: qualname,
scope: bytecode::NameScope::Free,
});
self.emit(Instruction::StoreLocal(qualname));
self.load_docstring(doc_str);
let doc = self.name("__doc__");
self.emit(Instruction::StoreName {
idx: doc,
scope: bytecode::NameScope::Free,
});
self.emit(Instruction::StoreLocal(doc));
// setup annotations
if self.find_ann(body) {
self.emit(Instruction::SetupAnnotation);
@@ -1153,9 +1246,7 @@ impl Compiler {
self.emit_constant(bytecode::ConstantData::None);
self.emit(Instruction::ReturnValue);
let mut code = self.pop_code_object();
code.flags.remove(bytecode::CodeFlags::NEW_LOCALS);
self.leave_scope();
let code = self.pop_code_object();
self.emit_constant(bytecode::ConstantData::Code {
code: Box::new(code),
@@ -1295,10 +1386,7 @@ impl Compiler {
self.set_label(check_asynciter_label);
self.emit(Instruction::Duplicate);
let stopasynciter = self.name("StopAsyncIteration");
self.emit(Instruction::LoadName {
idx: stopasynciter,
scope: bytecode::NameScope::Global,
});
self.emit(Instruction::LoadGlobal(stopasynciter));
self.emit(Instruction::CompareOperation {
op: bytecode::ComparisonOperator::ExceptionMatch,
});
@@ -1436,15 +1524,14 @@ impl Compiler {
if let ast::ExpressionType::Identifier { name } = &target.node {
// Store as dict entry in __annotations__ dict:
let annotations = self.name("__annotations__");
self.emit(Instruction::LoadName {
idx: annotations,
scope: bytecode::NameScope::Local,
});
self.emit_constant(bytecode::ConstantData::Str {
value: name.to_owned(),
});
self.emit(Instruction::StoreSubscript);
if !self.ctx.in_func() {
let annotations = self.name("__annotations__");
self.emit(Instruction::LoadLocal(annotations));
self.emit_constant(bytecode::ConstantData::Str {
value: name.to_owned(),
});
self.emit(Instruction::StoreSubscript);
}
} else {
// Drop annotation if not assigned to simple identifier.
self.emit(Instruction::Pop);
@@ -1846,6 +1933,7 @@ impl Compiler {
let prev_ctx = self.ctx;
self.ctx = CompileContext {
in_loop: false,
in_class: prev_ctx.in_class,
func: FunctionContext::Function,
};
@@ -1854,7 +1942,6 @@ impl Compiler {
self.compile_expression(body)?;
self.emit(Instruction::ReturnValue);
let code = self.pop_code_object();
self.leave_scope();
self.emit_constant(bytecode::ConstantData::Code {
code: Box::new(code),
});
@@ -2044,8 +2131,7 @@ impl Compiler {
line_number,
name.clone(),
));
let arg0 = self.name(".0");
self.enter_scope();
let arg0 = self.varname(".0");
// Create empty object of proper type:
match kind {
@@ -2079,10 +2165,7 @@ impl Compiler {
if loop_labels.is_empty() {
// Load iterator onto stack (passed as first argument):
self.emit(Instruction::LoadName {
idx: arg0,
scope: bytecode::NameScope::Local,
});
self.emit(Instruction::LoadFast(arg0));
} else {
// Evaluate iterated item:
self.compile_expression(&generator.iter)?;
@@ -2169,9 +2252,6 @@ impl Compiler {
// Fetch code for listcomp function:
let code = self.pop_code_object();
// Pop scope
self.leave_scope();
// List comprehension code:
self.emit_constant(bytecode::ConstantData::Code {
code: Box::new(code),
@@ -2255,33 +2335,6 @@ impl Compiler {
Ok(())
}
// Scope helpers:
fn enter_scope(&mut self) {
// println!("Enter scope {:?}", self.symbol_table_stack);
// Enter first subscope!
let table = self
.symbol_table_stack
.last_mut()
.unwrap()
.sub_tables
.remove(0);
self.symbol_table_stack.push(table);
}
fn leave_scope(&mut self) {
// println!("Leave scope {:?}", self.symbol_table_stack);
let table = self.symbol_table_stack.pop().unwrap();
assert!(table.sub_tables.is_empty());
}
fn lookup_name(&self, name: &str) -> &Symbol {
// println!("Looking up {:?}", name);
let symbol_table = self.symbol_table_stack.last().unwrap();
symbol_table.lookup(name).expect(
"The symbol must be present in the symbol table, even when it is undefined in python.",
)
}
// Low level helper functions:
fn emit(&mut self, instruction: Instruction) {
let location = compile_location(&self.current_source_location);
@@ -2402,9 +2455,11 @@ mod tests {
use rustpython_parser::parser;
fn compile_exec(source: &str) -> CodeObject {
let mut compiler: Compiler =
Compiler::new(CompileOpts::default(), "source_path".to_owned());
compiler.push_new_code_object("<module>".to_owned());
let mut compiler: Compiler = Compiler::new(
CompileOpts::default(),
"source_path".to_owned(),
"<module>".to_owned(),
);
let ast = parser::parse_program(source).unwrap();
let symbol_scope = make_symbol_table(&ast).unwrap();
compiler.compile_program(&ast, symbol_scope).unwrap();
+47 -38
View File
@@ -64,7 +64,7 @@ impl SymbolTable {
}
}
#[derive(Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SymbolTableType {
Module,
Class,
@@ -85,12 +85,14 @@ impl fmt::Display for SymbolTableType {
/// Indicator for a single symbol what the scope of this symbol is.
/// The scope can be unknown, which is unfortunate, but not impossible.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub enum SymbolScope {
Global,
Nonlocal,
Local,
Unknown,
Local,
GlobalExplicit,
GlobalImplicit,
Free,
Cell,
}
/// A single symbol in a table. Has various properties such as the scope
@@ -104,7 +106,6 @@ pub struct Symbol {
pub is_referenced: bool,
pub is_assigned: bool,
pub is_parameter: bool,
pub is_free: bool,
pub is_annotated: bool,
pub is_imported: bool,
@@ -126,7 +127,6 @@ impl Symbol {
is_referenced: false,
is_assigned: false,
is_parameter: false,
is_free: false,
is_annotated: false,
is_imported: false,
is_assign_namedexpr_in_comprehension: false,
@@ -135,7 +135,10 @@ impl Symbol {
}
pub fn is_global(&self) -> bool {
matches!(self.scope, SymbolScope::Global)
matches!(
self.scope,
SymbolScope::GlobalExplicit | SymbolScope::GlobalImplicit
)
}
pub fn is_local(&self) -> bool {
@@ -228,7 +231,7 @@ impl<'a> SymbolTableAnalyzer<'a> {
self.analyze_symbol_comprehension(symbol, 0)?
} else {
match symbol.scope {
SymbolScope::Nonlocal => {
SymbolScope::Free => {
let scope_depth = self.tables.len();
if scope_depth > 0 {
// check if the name is already defined in any outer scope
@@ -251,10 +254,10 @@ impl<'a> SymbolTableAnalyzer<'a> {
});
}
}
SymbolScope::Global => {
SymbolScope::GlobalExplicit | SymbolScope::GlobalImplicit => {
// TODO: add more checks for globals?
}
SymbolScope::Local => {
SymbolScope::Local | SymbolScope::Cell => {
// all is well
}
SymbolScope::Unknown => {
@@ -270,24 +273,28 @@ impl<'a> SymbolTableAnalyzer<'a> {
// Interesting stuff about the __class__ variable:
// https://docs.python.org/3/reference/datamodel.html?highlight=__class__#creating-the-class-object
symbol.name == "__class__"
|| self.tables.iter().skip(1).any(|(symbols, typ)| {
*typ != SymbolTableType::Class && symbols.contains_key(&symbol.name)
|| self.tables.iter().skip(1).rev().any(|(symbols, typ)| {
*typ != SymbolTableType::Class
&& symbols
.get(&symbol.name)
.map_or(false, |sym| sym.is_local() && sym.is_assigned)
})
}
fn analyze_unknown_symbol(&self, symbol: &mut Symbol) {
if symbol.is_assigned || symbol.is_parameter {
symbol.scope = SymbolScope::Local;
let scope = if symbol.is_assigned || symbol.is_parameter {
SymbolScope::Local
} else if self.found_in_outer_scope(symbol) {
// Symbol is in some outer scope.
symbol.is_free = true;
SymbolScope::Free
} else if self.tables.is_empty() {
// Don't make assumptions when we don't know.
symbol.scope = SymbolScope::Unknown;
SymbolScope::Unknown
} else {
// If there are scopes above we can assume global.
symbol.scope = SymbolScope::Global;
}
// If there are scopes above we assume global.
SymbolScope::GlobalImplicit
};
symbol.scope = scope;
}
// Implements the symbol analysis and scope extension for names
@@ -302,7 +309,7 @@ impl<'a> SymbolTableAnalyzer<'a> {
// when this is called, we expect to be in the direct parent scope of the scope that contains 'symbol'
let offs = self.tables.len() - 1 - parent_offset;
let last = self.tables.get_mut(offs).unwrap();
let symbols = &mut last.0;
let symbols = &mut *last.0;
let table_type = last.1;
// it is not allowed to use an iterator variable as assignee in a named expression
@@ -319,7 +326,7 @@ impl<'a> SymbolTableAnalyzer<'a> {
match table_type {
SymbolTableType::Module => {
symbol.scope = SymbolScope::Global;
symbol.scope = SymbolScope::GlobalImplicit;
}
SymbolTableType::Class => {
// named expressions are forbidden in comprehensions on class scope
@@ -327,22 +334,18 @@ impl<'a> SymbolTableAnalyzer<'a> {
error: "assignment expression within a comprehension cannot be used in a class body".to_string(),
// TODO: accurate location info, somehow
location: Location::default(),
} );
});
}
SymbolTableType::Function => {
if let Some(parent_symbol) = symbols.get_mut(&symbol.name) {
if let SymbolScope::Unknown = parent_symbol.scope {
parent_symbol.is_assigned = true; // this information is new, as the asignment is done in inner scope
//self.analyze_unknown_symbol(symbol); // not needed, symbol is analyzed anyhow when its scope is analyzed
// this information is new, as the asignment is done in inner scope
parent_symbol.is_assigned = true;
//self.analyze_unknown_symbol(symbol); // not needed, symbol is analyzed anyhow when its scope is analyzed
}
match symbol.scope {
SymbolScope::Global => {
symbol.scope = SymbolScope::Global;
}
_ => {
symbol.scope = SymbolScope::Nonlocal;
}
if !symbol.is_global() {
symbol.scope = SymbolScope::Free;
}
} else {
let mut cloned_sym = symbol.clone();
@@ -372,7 +375,7 @@ impl<'a> SymbolTableAnalyzer<'a> {
// ouside, too, and set it therefore to non-local scope. I.e., we expect to
// find a definition on a higher level
let mut cloned_sym = symbol.clone();
cloned_sym.scope = SymbolScope::Nonlocal;
cloned_sym.scope = SymbolScope::Free;
last.0.insert(cloned_sym.name.to_owned(), cloned_sym);
}
}
@@ -408,7 +411,7 @@ struct SymbolTableBuilder {
/// was used.
/// In cpython this is stored in the AST, but I think this
/// is not logical, since it is not context free.
#[derive(Copy, Clone)]
#[derive(Copy, Clone, PartialEq)]
enum ExpressionContext {
Load,
Store,
@@ -825,6 +828,12 @@ impl SymbolTableBuilder {
self.register_name(name, SymbolUsage::Iter, location)?;
}
}
if context == ExpressionContext::Load
&& self.tables.last().unwrap().typ == SymbolTableType::Function
&& name == "super"
{
self.register_name("__class__", SymbolUsage::Used, location)?;
}
}
Lambda { args, body } => {
self.enter_function("lambda", args, expression.location.row())?;
@@ -949,7 +958,7 @@ impl SymbolTableBuilder {
// Role already set..
match role {
SymbolUsage::Global => {
if let SymbolScope::Global = symbol.scope {
if symbol.is_global() {
// Ok
} else {
return Err(SymbolTableError {
@@ -1014,7 +1023,7 @@ impl SymbolTableBuilder {
let symbol = table.symbols.get_mut(name).unwrap();
match role {
SymbolUsage::Nonlocal => {
symbol.scope = SymbolScope::Nonlocal;
symbol.scope = SymbolScope::Free;
}
SymbolUsage::Imported => {
symbol.is_assigned = true;
@@ -1040,8 +1049,8 @@ impl SymbolTableBuilder {
}
SymbolUsage::Global => {
if let SymbolScope::Unknown = symbol.scope {
symbol.scope = SymbolScope::Global;
} else if let SymbolScope::Global = symbol.scope {
symbol.scope = SymbolScope::GlobalImplicit;
} else if symbol.is_global() {
// Global scope can be set to global
} else {
return Err(SymbolTableError {
+3 -4
View File
@@ -500,19 +500,18 @@ where
self.as_object().get_item(key, vm)
}
fn set_item(&self, key: K, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
fn set_item(&self, key: K, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
if self.class().is(&vm.ctx.types.dict_type) {
self.inner_setitem_fast(key, value, vm)
.map(|_| vm.ctx.none())
} else {
// Fall back to slow path if we are in a dict subclass:
self.as_object().set_item(key, value, vm)
}
}
fn del_item(&self, key: K, vm: &VirtualMachine) -> PyResult {
fn del_item(&self, key: K, vm: &VirtualMachine) -> PyResult<()> {
if self.class().is(&vm.ctx.types.dict_type) {
self.entries.delete(vm, key).map(|_| vm.ctx.none())
self.entries.delete(vm, key)
} else {
// Fall back to slow path if we are in a dict subclass:
self.as_object().del_item(key, vm)
+3 -3
View File
@@ -44,12 +44,12 @@ impl FrameRef {
#[pyproperty]
fn f_globals(self) -> PyDictRef {
self.scope.globals.clone()
self.globals.clone()
}
#[pyproperty]
fn f_locals(self) -> PyDictRef {
self.scope.get_locals().clone()
fn f_locals(self, vm: &VirtualMachine) -> PyDictRef {
self.locals(vm).clone()
}
#[pyproperty]
+47 -39
View File
@@ -5,7 +5,7 @@ use super::code::PyCodeRef;
use super::dict::PyDictRef;
use super::pystr::PyStrRef;
use super::pytype::PyTypeRef;
use super::tuple::PyTupleRef;
use super::tuple::{PyTupleRef, PyTupleTyped};
use crate::builtins::asyncgenerator::PyAsyncGen;
use crate::builtins::coroutine::PyCoroutine;
use crate::builtins::generator::PyGenerator;
@@ -36,7 +36,8 @@ pub struct PyFunction {
code: PyCodeRef,
#[cfg(feature = "jit")]
jitted_code: OnceCell<CompiledCode>,
scope: Scope,
globals: PyDictRef,
closure: Option<PyTupleTyped<PyCellRef>>,
defaults: Option<PyTupleRef>,
kw_only_defaults: Option<PyDictRef>,
}
@@ -44,7 +45,8 @@ pub struct PyFunction {
impl PyFunction {
pub fn new(
code: PyCodeRef,
scope: Scope,
globals: PyDictRef,
closure: Option<PyTupleTyped<PyCellRef>>,
defaults: Option<PyTupleRef>,
kw_only_defaults: Option<PyDictRef>,
) -> Self {
@@ -52,25 +54,23 @@ impl PyFunction {
code,
#[cfg(feature = "jit")]
jitted_code: OnceCell::new(),
scope,
globals,
closure,
defaults,
kw_only_defaults,
}
}
pub fn scope(&self) -> &Scope {
&self.scope
}
fn fill_locals_from_args(
&self,
locals: &PyDictRef,
frame: &Frame,
func_args: FuncArgs,
vm: &VirtualMachine,
) -> PyResult<()> {
let code = &*self.code;
let nargs = func_args.args.len();
let nexpected_args = self.code.arg_count;
let arg_names = self.code.arg_names();
let nexpected_args = code.arg_count;
// let arg_names = self.code.arg_names();
// This parses the arguments from args and kwargs into
// the proper variables keeping into account default values
@@ -78,23 +78,24 @@ impl PyFunction {
// 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
};
let n = std::cmp::min(nargs, nexpected_args);
let mut args_iter = func_args.args.into_iter();
let mut fastlocals = frame.fastlocals.lock();
// let mut fill_locals = fastlocals.iter_mut().enumerate();
// Copy positional arguments into local variables
for (arg, arg_name) in args_iter.by_ref().take(n).zip(arg_names.args) {
locals.set_item(arg_name.clone(), arg, vm)?;
for (i, arg) in args_iter.by_ref().take(n).enumerate() {
fastlocals[i] = Some(arg);
}
let mut vararg_offset = code.arg_count;
// Pack other positional arguments in to *args:
if let Some(vararg_name) = arg_names.vararg {
if code.flags.contains(bytecode::CodeFlags::HAS_VARARGS) {
let vararg_value = vm.ctx.new_tuple(args_iter.collect());
locals.set_item(vararg_name.clone(), vararg_value, vm)?;
fastlocals[vararg_offset] = Some(vararg_value);
vararg_offset += 1;
} else {
// Check the number of positional arguments
if nargs > nexpected_args {
@@ -106,25 +107,25 @@ impl PyFunction {
}
// Do we support `**kwargs` ?
let kwargs = if let Some(varkwarg) = arg_names.varkwarg {
let kwargs = if code.flags.contains(bytecode::CodeFlags::HAS_VARKEYWORDS) {
let d = vm.ctx.new_dict();
locals.set_item(varkwarg.clone(), d.as_object().clone(), vm)?;
fastlocals[vararg_offset] = Some(d.clone());
Some(d)
} else {
None
};
let contains_arg =
|names: &[PyStrRef], name: &str| names.iter().any(|s| s.borrow_value() == name);
// let argpos =
// |names: &[PyStrRef], name: &str| names.iter().position(|s| s.borrow_value() == name);
let mut posonly_passed_as_kwarg = Vec::new();
// Handle keyword arguments
for (name, value) in func_args.kwargs {
// Check if we have a parameter with this name:
let dict = if contains_arg(arg_names.args, &name)
|| contains_arg(arg_names.kwonlyargs, &name)
let dict = if let Some(pos) =
argpos(arg_names.args, &name).or_else(|| argpos(arg_names.kwonlyargs, &name))
{
if contains_arg(arg_names.posonlyargs, &name) {
if argpos(arg_names.posonlyargs, &name) {
posonly_passed_as_kwarg.push(name);
continue;
} else if locals.contains_key(&name, vm) {
@@ -138,7 +139,7 @@ impl PyFunction {
vm.new_type_error(format!("Got an unexpected keyword argument '{}'", name))
})?
};
dict.set_item(vm.intern_string(name).into_object(), value, vm)?;
// dict.set_item(vm.intern_string(name).into_object(), value, vm)?;
}
if !posonly_passed_as_kwarg.is_empty() {
return Err(vm.new_type_error(format!(
@@ -204,10 +205,10 @@ impl PyFunction {
Ok(())
}
pub fn invoke_with_scope(
pub fn invoke_with_locals(
&self,
func_args: FuncArgs,
scope: &Scope,
locals: Option<PyDictRef>,
vm: &VirtualMachine,
) -> PyResult {
#[cfg(feature = "jit")]
@@ -226,16 +227,22 @@ impl PyFunction {
let code = &self.code;
let scope = if self.code.flags.contains(bytecode::CodeFlags::NEW_LOCALS) {
scope.new_child_scope(&vm.ctx)
let locals = if self.code.flags.contains(bytecode::CodeFlags::NEW_LOCALS) {
vm.ctx.new_dict()
} else {
scope.clone()
locals.unwrap_or_else(|| self.globals.clone())
};
self.fill_locals_from_args(&scope.get_locals(), func_args, vm)?;
// Construct frame:
let frame = Frame::new(code.clone(), scope, vm).into_ref(vm);
let frame = Frame::new(
code.clone(),
Scope::new(Some(locals), self.globals.clone()),
self.closure.as_ref().map_or(&[], |c| c.borrow_value()),
vm,
)
.into_ref(vm);
self.fill_locals_from_args(&frame, func_args, vm)?;
// If we have a generator, create a new generator
let is_gen = code.flags.contains(bytecode::CodeFlags::IS_GENERATOR);
@@ -249,7 +256,7 @@ impl PyFunction {
}
pub fn invoke(&self, func_args: FuncArgs, vm: &VirtualMachine) -> PyResult {
self.invoke_with_scope(func_args, &self.scope, vm)
self.invoke_with_locals(func_args, None, vm)
}
}
@@ -278,7 +285,7 @@ impl PyFunction {
#[pyproperty(magic)]
fn globals(&self) -> PyDictRef {
self.scope.globals.clone()
self.globals.clone()
}
#[cfg(feature = "jit")]
@@ -393,10 +400,11 @@ impl PyValue for PyBoundMethod {
}
#[pyclass(module = false, name = "cell")]
#[derive(Debug)]
#[derive(Debug, Default)]
pub(crate) struct PyCell {
contents: PyMutex<Option<PyObjectRef>>,
}
pub(crate) type PyCellRef = PyRef<PyCell>;
impl PyValue for PyCell {
fn class(vm: &VirtualMachine) -> &PyTypeRef {
+8 -16
View File
@@ -172,7 +172,7 @@ mod decl {
fn dir(obj: OptionalArg<PyObjectRef>, vm: &VirtualMachine) -> PyResult<PyList> {
let seq = match obj {
OptionalArg::Present(obj) => vm.call_method(&obj, "__dir__", ())?,
OptionalArg::Missing => vm.call_method(&vm.get_locals().into_object(), "keys", ())?,
OptionalArg::Missing => vm.call_method(vm.current_locals().as_object(), "keys", ())?,
};
let sorted = sorted(seq, Default::default(), vm)?;
Ok(sorted)
@@ -243,14 +243,13 @@ mod decl {
#[cfg(feature = "rustpython-compiler")]
fn make_scope(vm: &VirtualMachine, scope: ScopeArgs) -> PyResult<Scope> {
let globals = scope.globals;
let current_scope = vm.current_scope();
let locals = match scope.locals {
Some(dict) => Some(dict),
None => {
if globals.is_some() {
None
} else {
current_scope.get_only_locals()
Some(vm.current_locals().clone())
}
}
};
@@ -262,7 +261,7 @@ mod decl {
}
dict
}
None => current_scope.globals.clone(),
None => vm.current_globals().clone(),
};
let scope = Scope::with_builtins(locals, globals, vm);
@@ -318,7 +317,7 @@ mod decl {
#[pyfunction]
fn globals(vm: &VirtualMachine) -> PyResult<PyDictRef> {
Ok(vm.current_scope().globals.clone())
Ok(vm.current_globals().clone())
}
#[pyfunction]
@@ -449,7 +448,7 @@ mod decl {
#[pyfunction]
fn locals(vm: &VirtualMachine) -> PyDictRef {
let locals = vm.get_locals();
let locals = vm.current_locals();
locals.copy().into_ref(vm)
}
@@ -797,7 +796,7 @@ mod decl {
if let OptionalArg::Present(obj) = obj {
vm.get_attribute(obj, "__dict__")
} else {
Ok(vm.get_locals().into_object())
Ok(vm.current_locals().clone().into_object())
}
}
@@ -843,20 +842,13 @@ mod decl {
let namespace: PyDictRef = TryFromObject::try_from_object(vm, namespace)?;
let cells = vm.ctx.new_dict();
let scope = function
.scope()
.new_child_scope_with_locals(cells.clone())
.new_child_scope_with_locals(namespace.clone());
function.invoke_with_scope(().into(), &scope, vm)?;
function.invoke_with_locals(().into(), Some(namespace), vm)?;
let class = vm.invoke(
metaclass.as_object(),
FuncArgs::new(vec![name_obj, bases, namespace.into_object()], kwargs),
)?;
cells.set_item("__class__", class.clone(), vm)?;
// cells.set_item("__class__", class.clone(), vm)?;
Ok(class)
}
}
+26 -12
View File
@@ -61,16 +61,30 @@ impl PySuper {
let typ = if let OptionalArg::Present(ty) = py_type {
ty
} else {
let obj = vm
.current_scope()
.load_cell(vm, "__class__")
.ok_or_else(|| {
vm.new_type_error(
"super must be called with 1 argument or from inside class method"
.to_owned(),
)
})?;
PyTypeRef::try_from_object(vm, obj)?
let frame = vm
.current_frame()
.expect("super() called from outside of frame?");
let mut typ = None;
for (i, var) in frame.code.freevars.iter().enumerate() {
if var.borrow_value() == "__class__" {
let i = frame.code.cellvars.len() + i;
let class = frame.cells_frees[i].get().ok_or_else(|| {
vm.new_runtime_error("super(): empty __class__ cell".to_owned())
})?;
typ = Some(class.downcast().map_err(|o| {
vm.new_type_error(format!(
"super(): __class__ is not a type ({})",
o.class().name
))
})?);
break;
}
}
typ.ok_or_else(|| {
vm.new_type_error(
"super must be called with 1 argument or from inside class method".to_owned(),
)
})?
};
// Check type argument:
@@ -87,8 +101,8 @@ impl PySuper {
} else {
let frame = vm.current_frame().expect("no current frame for super()");
if let Some(first_arg) = frame.code.arg_names().args.get(0) {
let locals = frame.scope.get_locals();
locals
frame
.locals
.get_item_option(first_arg.clone(), vm)?
.ok_or_else(|| {
vm.new_type_error(format!("super argument {} was not supplied", first_arg))
+8 -2
View File
@@ -294,7 +294,7 @@ pub(crate) fn init(context: &PyContext) {
PyTupleIterator::extend_class(context, &context.types.tuple_iterator_type);
}
pub struct PyTupleTyped<T> {
pub struct PyTupleTyped<T: TransmuteFromObject> {
// SAFETY INVARIANT: T must be repr(transparent) over PyObjectRef, and the
// elements must be logically valid when transmuted to T
tuple: PyTupleRef,
@@ -315,9 +315,15 @@ impl<T: TransmuteFromObject> TryFromObject for PyTupleTyped<T> {
}
}
impl<'a, T: 'a> BorrowValue<'a> for PyTupleTyped<T> {
impl<'a, T: TransmuteFromObject + 'a> BorrowValue<'a> for PyTupleTyped<T> {
type Borrowed = &'a [T];
fn borrow_value(&'a self) -> Self::Borrowed {
unsafe { &*(self.tuple.borrow_value() as *const [PyObjectRef] as *const [T]) }
}
}
impl<T: TransmuteFromObject + fmt::Debug> fmt::Debug for PyTupleTyped<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.borrow_value().fmt(f)
}
}
+165 -77
View File
@@ -9,12 +9,13 @@ use crate::builtins::asyncgenerator::PyAsyncGenWrappedValue;
use crate::builtins::code::PyCodeRef;
use crate::builtins::coroutine::PyCoroutine;
use crate::builtins::dict::{PyDict, PyDictRef};
use crate::builtins::function::{PyCell, PyCellRef, PyFunction};
use crate::builtins::generator::PyGenerator;
use crate::builtins::pystr::{self, PyStr, PyStrRef};
use crate::builtins::pytype::PyTypeRef;
use crate::builtins::slice::PySlice;
use crate::builtins::traceback::PyTraceback;
use crate::builtins::tuple::PyTuple;
use crate::builtins::tuple::{PyTuple, PyTupleTyped};
use crate::builtins::{list, pybool, set};
use crate::bytecode;
use crate::common::lock::PyMutex;
@@ -91,7 +92,12 @@ struct FrameState {
#[pyclass(module = false, name = "frame")]
pub struct Frame {
pub code: PyCodeRef,
pub scope: Scope,
pub fastlocals: PyMutex<Box<[Option<PyObjectRef>]>>,
pub cells_frees: Box<[PyCellRef]>,
pub locals: PyDictRef,
pub globals: PyDictRef,
/// index of last instruction ran
pub lasti: AtomicUsize,
/// tracer function for this frame (usually is None)
@@ -151,7 +157,7 @@ impl ExecutionResult {
pub type FrameResult = PyResult<Option<ExecutionResult>>;
impl Frame {
pub fn new(code: PyCodeRef, scope: Scope, vm: &VirtualMachine) -> Frame {
pub fn new(code: PyCodeRef, scope: Scope, closure: &[PyCellRef], vm: &VirtualMachine) -> Frame {
//populate the globals and locals
//TODO: This is wrong, check https://github.com/nedbat/byterun/blob/31e6c4a8212c35b5157919abff43a7daa0f377c6/byterun/pyvm2.py#L95
/*
@@ -162,10 +168,17 @@ impl Frame {
*/
// let locals = globals;
// locals.extend(callargs);
let cells_frees = std::iter::repeat_with(|| PyCell::default().into_ref(vm))
.take(code.cellvars.len())
.chain(closure.iter().cloned())
.collect();
Frame {
fastlocals: PyMutex::new(vec![None; code.varnames.len()].into_boxed_slice()),
cells_frees,
locals: scope.locals,
globals: scope.globals,
code,
scope,
lasti: AtomicUsize::new(0),
state: PyMutex::new(FrameState {
stack: Vec::new(),
@@ -182,7 +195,10 @@ impl FrameRef {
let mut state = self.state.lock();
let exec = ExecutingFrame {
code: &self.code,
scope: &self.scope,
fastlocals: &self.fastlocals,
cells_frees: &self.cells_frees,
locals: &self.locals,
globals: &self.globals,
lasti: &self.lasti,
object: &self,
state: &mut state,
@@ -190,6 +206,10 @@ impl FrameRef {
f(exec)
}
pub fn locals(&self, vm: &VirtualMachine) -> &PyDictRef {
todo!()
}
// #[cfg_attr(feature = "flame-it", flame("Frame"))]
pub fn run(&self, vm: &VirtualMachine) -> PyResult<ExecutionResult> {
self.with_exec(|mut exec| exec.run(vm))
@@ -233,7 +253,10 @@ impl FrameRef {
/// with the mutable data inside
struct ExecutingFrame<'a> {
code: &'a PyCodeRef,
scope: &'a Scope,
fastlocals: &'a PyMutex<Box<[Option<PyObjectRef>]>>,
cells_frees: &'a [PyCellRef],
locals: &'a PyDictRef,
globals: &'a PyDictRef,
object: &'a FrameRef,
lasti: &'a AtomicUsize,
state: &'a mut FrameState,
@@ -243,7 +266,7 @@ impl fmt::Debug for ExecutingFrame<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ExecutingFrame")
.field("code", self.code)
.field("scope", self.scope)
// .field("scope", self.scope)
.field("lasti", self.lasti)
.field("state", self.state)
.finish()
@@ -330,6 +353,24 @@ impl ExecutingFrame<'_> {
}
}
fn unbound_cell_exception(&self, i: usize, vm: &VirtualMachine) -> PyBaseExceptionRef {
if let Some(name) = self.code.cellvars.get(i) {
vm.new_exception_msg(
vm.ctx.exceptions.unbound_local_error.clone(),
format!(
"local variable '{}' referenced before assignment",
self.code.cellvars[i]
),
)
} else {
let name = &self.code.freevars[i - self.code.cellvars.len()];
vm.new_name_error(format!(
"free variable '{}' referenced before assignment in enclosing scope",
name
))
}
}
/// Execute a single instruction.
#[inline(always)]
fn execute_instruction(
@@ -366,9 +407,100 @@ impl ExecutingFrame<'_> {
} => self.import(vm, *name_idx, symbols_idx, *level),
bytecode::Instruction::ImportStar => self.import_star(vm),
bytecode::Instruction::ImportFrom { idx } => self.import_from(vm, *idx),
bytecode::Instruction::LoadName { idx, scope } => self.load_name(vm, *idx, *scope),
bytecode::Instruction::StoreName { idx, scope } => self.store_name(vm, *idx, *scope),
bytecode::Instruction::DeleteName { idx } => self.delete_name(vm, *idx),
bytecode::Instruction::LoadFast(idx) => {
let x = self.fastlocals.lock()[*idx].clone().ok_or_else(|| {
vm.new_exception_msg(
vm.ctx.exceptions.unbound_local_error.clone(),
format!(
"local variable '{}' referenced before assignment",
self.code.varnames[*idx]
),
)
})?;
self.push_value(x);
Ok(None)
}
bytecode::Instruction::LoadLocal(idx) => {
let name = &self.code.names[*idx];
let x = self
.locals
.get_item_option(name.clone(), vm)?
.ok_or_else(|| vm.new_name_error(format!("name '{}' is not defined", name)))?;
self.push_value(x);
Ok(None)
}
bytecode::Instruction::LoadGlobal(idx) => {
let name = &self.code.names[*idx];
let x = self
.globals
.get_item_option(name.clone(), vm)?
.ok_or_else(|| vm.new_name_error(format!("name '{}' is not defined", name)))?;
self.push_value(x);
Ok(None)
}
bytecode::Instruction::LoadDeref(i) => {
let i = *i;
self.cells_frees[i]
.get()
.ok_or_else(|| self.unbound_cell_exception(i, vm))?;
Ok(None)
}
bytecode::Instruction::LoadClassDeref(i) => {
let i = *i;
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))?
};
self.push_value(value);
Ok(None)
}
bytecode::Instruction::StoreFast(idx) => {
let value = self.pop_value();
self.fastlocals.lock()[*idx] = Some(value);
Ok(None)
}
bytecode::Instruction::StoreLocal(idx) => {
let value = self.pop_value();
self.locals
.set_item(self.code.names[*idx].clone(), value, vm)?;
Ok(None)
}
bytecode::Instruction::StoreGlobal(idx) => {
let value = self.pop_value();
self.globals
.set_item(self.code.names[*idx].clone(), value, vm)?;
Ok(None)
}
bytecode::Instruction::StoreDeref(i) => {
let value = self.pop_value();
self.cells_frees[*i].set(Some(value));
Ok(None)
}
bytecode::Instruction::DeleteFast(idx) => {
self.fastlocals.lock()[*idx] = None;
Ok(None)
}
bytecode::Instruction::DeleteLocal(idx) => {
self.locals.del_item(self.code.names[*idx].clone(), vm)?;
Ok(None)
}
bytecode::Instruction::DeleteGlobal(idx) => {
self.globals.del_item(self.code.names[*idx].clone(), vm)?;
Ok(None)
}
bytecode::Instruction::DeleteDeref(i) => {
self.cells_frees[*i].set(None);
Ok(None)
}
bytecode::Instruction::LoadClosure(i) => {
let value = self.cells_frees[*i].clone();
self.push_value(value.into_object());
Ok(None)
}
bytecode::Instruction::Subscript => self.execute_subscript(vm),
bytecode::Instruction::StoreSubscript => self.execute_store_subscript(vm),
bytecode::Instruction::DeleteSubscript => self.execute_delete_subscript(vm),
@@ -472,9 +604,9 @@ impl ExecutingFrame<'_> {
}
bytecode::Instruction::YieldFrom => self.execute_yield_from(vm),
bytecode::Instruction::SetupAnnotation => {
let locals = self.scope.get_locals();
if !locals.contains_key("__annotations__", vm) {
locals.set_item("__annotations__", vm.ctx.new_dict().into_object(), vm)?;
if !self.locals.contains_key("__annotations__", vm) {
self.locals
.set_item("__annotations__", vm.ctx.new_dict().into_object(), vm)?;
}
Ok(None)
}
@@ -826,9 +958,8 @@ impl ExecutingFrame<'_> {
};
for (k, v) in &dict {
let k = PyStrRef::try_from_object(vm, k)?;
let k = k.as_ref();
if filter_pred(k) {
self.scope.store_name(&vm, k, v);
if filter_pred(k.borrow_value()) {
self.locals.set_item(k, v, vm);
}
}
}
@@ -904,61 +1035,6 @@ impl ExecutingFrame<'_> {
}
}
fn store_name(
&mut self,
vm: &VirtualMachine,
idx: bytecode::NameIdx,
name_scope: bytecode::NameScope,
) -> FrameResult {
let name = self.code.names[idx].clone();
let obj = self.pop_value();
match name_scope {
bytecode::NameScope::Global => {
self.scope.store_global(vm, name, obj);
}
bytecode::NameScope::NonLocal => {
self.scope.store_cell(vm, name, obj);
}
bytecode::NameScope::Local => {
self.scope.store_name(vm, name, obj);
}
bytecode::NameScope::Free => {
self.scope.store_name(vm, name, obj);
}
}
Ok(None)
}
fn delete_name(&self, vm: &VirtualMachine, idx: bytecode::NameIdx) -> FrameResult {
let name = &self.code.names[idx];
match self.scope.delete_name(vm, name.clone()) {
Ok(_) => Ok(None),
Err(_) => Err(vm.new_name_error(format!("name '{}' is not defined", name))),
}
}
#[cfg_attr(feature = "flame-it", flame("Frame"))]
#[inline]
fn load_name(
&mut self,
vm: &VirtualMachine,
idx: bytecode::NameIdx,
name_scope: bytecode::NameScope,
) -> FrameResult {
let name = &self.code.names[idx];
let optional_value = match name_scope {
bytecode::NameScope::Global => self.scope.load_global(vm, name.clone()),
bytecode::NameScope::NonLocal => self.scope.load_cell(vm, name.clone()),
bytecode::NameScope::Local => self.scope.load_local(vm, name.clone()),
bytecode::NameScope::Free => self.scope.load_name(vm, name.clone()),
};
let value = optional_value
.ok_or_else(|| vm.new_name_error(format!("name '{}' is not defined", name)))?;
self.push_value(value);
Ok(None)
}
fn execute_rotate(&mut self, amount: usize) -> FrameResult {
// Shuffles top of stack amount down
if amount < 2 {
@@ -1278,6 +1354,12 @@ impl ExecutingFrame<'_> {
let flags = code_obj.flags;
let closure = if code_obj.freevars.is_empty() {
None
} else {
Some(PyTupleTyped::try_from_object(vm, self.pop_value()).unwrap())
};
let annotations = if flags.contains(bytecode::CodeFlags::HAS_ANNOTATIONS) {
self.pop_value()
} else {
@@ -1306,10 +1388,15 @@ impl ExecutingFrame<'_> {
// pop argc arguments
// argument: name, args, globals
let scope = self.scope.clone();
let func_obj = vm
.ctx
.new_pyfunction(code_obj, scope, defaults, kw_only_defaults);
// let scope = self.scope.clone();
let func_obj = PyFunction::new(
code_obj,
self.globals.clone(),
closure,
defaults,
kw_only_defaults,
)
.into_object(vm);
vm.set_attr(&func_obj, "__doc__", vm.ctx.none())?;
@@ -1320,7 +1407,7 @@ impl ExecutingFrame<'_> {
.unwrap();
vm.set_attr(&func_obj, "__name__", vm.ctx.new_str(name))?;
vm.set_attr(&func_obj, "__qualname__", qualified_name)?;
let module = vm.unwrap_or_none(self.scope.globals.get_item_option("__name__", vm)?);
let module = vm.unwrap_or_none(self.globals.get_item_option("__name__", vm)?);
vm.set_attr(&func_obj, "__module__", module)?;
vm.set_attr(&func_obj, "__annotations__", annotations)?;
@@ -1546,7 +1633,8 @@ impl fmt::Debug for Frame {
.iter()
.map(|elem| format!("\n > {:?}", elem))
.collect::<String>();
let dict = self.scope.get_locals();
// TODO: fix this up
let dict = self.locals.clone();
let local_str = dict
.into_iter()
.map(|elem| format!("\n {:?} = {:?}", elem.0, elem.1))
+6 -20
View File
@@ -348,20 +348,6 @@ impl PyContext {
PyRef::new_ref(code::PyCode::new(code), self.types.code_type.clone(), None)
}
pub fn new_pyfunction(
&self,
code_obj: PyCodeRef,
scope: Scope,
defaults: Option<PyTupleRef>,
kw_only_defaults: Option<PyDictRef>,
) -> PyObjectRef {
PyObject::new(
PyFunction::new(code_obj, scope, defaults, kw_only_defaults),
self.types.function_type.clone(),
Some(self.new_dict()),
)
}
pub fn new_bound_method(&self, function: PyObjectRef, object: PyObjectRef) -> PyObjectRef {
PyObject::new(
PyBoundMethod::new(object, function),
@@ -637,8 +623,8 @@ where
T: IntoPyObject + ?Sized,
{
fn get_item(&self, key: T, vm: &VirtualMachine) -> PyResult;
fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine) -> PyResult;
fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult;
fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()>;
fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult<()>;
}
impl<T> ItemProtocol<T> for PyObjectRef
@@ -649,12 +635,12 @@ where
vm.call_method(self, "__getitem__", (key,))
}
fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
vm.call_method(self, "__setitem__", (key, value))
fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
vm.call_method(self, "__setitem__", (key, value)).map(drop)
}
fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult {
vm.call_method(self, "__delitem__", (key,))
fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult<()> {
vm.call_method(self, "__delitem__", (key,)).map(drop)
}
}
+95 -103
View File
@@ -1,17 +1,13 @@
use std::fmt;
use crate::builtins::{PyDictRef, PyStr, PyStrRef};
use crate::common::lock::PyMutex;
use crate::pyobject::{IntoPyObject, ItemProtocol, PyContext, PyObjectRef, PyResult, TryIntoRef};
use crate::VirtualMachine;
/*
* So a scope is a linked list of scopes.
* When a name is looked up, it is check in its scope.
*/
#[derive(Clone)]
pub struct Scope {
locals: Vec<PyDictRef>,
pub globals: PyDictRef,
pub(crate) locals: PyDictRef,
pub(crate) globals: PyDictRef,
}
impl fmt::Debug for Scope {
@@ -22,14 +18,10 @@ impl fmt::Debug for Scope {
}
impl Scope {
pub fn new(locals: Option<PyDictRef>, globals: PyDictRef, vm: &VirtualMachine) -> Scope {
let locals = match locals {
Some(dict) => vec![dict],
None => vec![],
};
let scope = Scope { locals, globals };
scope.store_name(vm, "__annotations__", vm.ctx.new_dict().into_object());
scope
#[inline]
pub fn new(locals: Option<PyDictRef>, globals: PyDictRef) -> Scope {
let locals = locals.unwrap_or_else(|| globals.clone());
Scope { locals, globals }
}
pub fn with_builtins(
@@ -42,108 +34,108 @@ impl Scope {
.set_item("__builtins__", vm.builtins.clone(), vm)
.unwrap();
}
Scope::new(locals, globals, vm)
Scope::new(locals, globals)
}
pub fn get_locals(&self) -> &PyDictRef {
match self.locals.first() {
Some(dict) => dict,
None => &self.globals,
}
}
// pub fn get_locals(&self) -> &PyDictRef {
// match self.locals.first() {
// Some(dict) => dict,
// None => &self.globals,
// }
// }
pub fn get_only_locals(&self) -> Option<PyDictRef> {
self.locals.first().cloned()
}
// pub fn get_only_locals(&self) -> Option<PyDictRef> {
// self.locals.first().cloned()
// }
pub fn new_child_scope_with_locals(&self, locals: PyDictRef) -> Scope {
let mut new_locals = Vec::with_capacity(self.locals.len() + 1);
new_locals.push(locals);
new_locals.extend_from_slice(&self.locals);
Scope {
locals: new_locals,
globals: self.globals.clone(),
}
}
// pub fn new_child_scope_with_locals(&self, locals: PyDictRef) -> Scope {
// let mut new_locals = Vec::with_capacity(self.locals.len() + 1);
// new_locals.push(locals);
// new_locals.extend_from_slice(&self.locals);
// Scope {
// locals: new_locals,
// globals: self.globals.clone(),
// }
// }
pub fn new_child_scope(&self, ctx: &PyContext) -> Scope {
self.new_child_scope_with_locals(ctx.new_dict())
}
// pub fn new_child_scope(&self, ctx: &PyContext) -> Scope {
// self.new_child_scope_with_locals(ctx.new_dict())
// }
#[cfg_attr(feature = "flame-it", flame("Scope"))]
pub fn load_name(&self, vm: &VirtualMachine, name: impl PyName) -> Option<PyObjectRef> {
for dict in self.locals.iter() {
if let Some(value) = dict.get_item_option(name.clone(), vm).unwrap() {
return Some(value);
}
}
// #[cfg_attr(feature = "flame-it", flame("Scope"))]
// pub fn load_name(&self, vm: &VirtualMachine, name: impl PyName) -> Option<PyObjectRef> {
// for dict in self.locals.iter() {
// if let Some(value) = dict.get_item_option(name.clone(), vm).unwrap() {
// return Some(value);
// }
// }
// Fall back to loading a global after all scopes have been searched!
self.load_global(vm, name)
}
// // Fall back to loading a global after all scopes have been searched!
// self.load_global(vm, name)
// }
#[cfg_attr(feature = "flame-it", flame("Scope"))]
/// Load a local name. Only check the local dictionary for the given name.
pub fn load_local(&self, vm: &VirtualMachine, name: impl PyName) -> Option<PyObjectRef> {
self.get_locals().get_item_option(name, vm).unwrap()
}
// #[cfg_attr(feature = "flame-it", flame("Scope"))]
// /// Load a local name. Only check the local dictionary for the given name.
// pub fn load_local(&self, vm: &VirtualMachine, name: impl PyName) -> Option<PyObjectRef> {
// self.get_locals().get_item_option(name, vm).unwrap()
// }
#[cfg_attr(feature = "flame-it", flame("Scope"))]
pub fn load_cell(&self, vm: &VirtualMachine, name: impl PyName) -> Option<PyObjectRef> {
for dict in self.locals.iter().skip(1) {
if let Some(value) = dict.get_item_option(name.clone(), vm).unwrap() {
return Some(value);
}
}
None
}
// #[cfg_attr(feature = "flame-it", flame("Scope"))]
// pub fn load_cell(&self, vm: &VirtualMachine, name: impl PyName) -> Option<PyObjectRef> {
// for dict in self.locals.iter().skip(1) {
// if let Some(value) = dict.get_item_option(name.clone(), vm).unwrap() {
// return Some(value);
// }
// }
// None
// }
pub fn store_cell(&self, vm: &VirtualMachine, name: impl PyName, value: PyObjectRef) {
// find the innermost outer scope that contains the symbol name
if let Some(locals) = self
.locals
.iter()
.rev()
.find(|l| l.contains_key(name.clone(), vm))
{
// add to the symbol
locals.set_item(name, value, vm).unwrap();
} else {
// somewhat limited solution -> fallback to the old rustpython strategy
// and store the next outer scope
// This case is usually considered as a failure case, but kept for the moment
// to support the scope propagation for named expression assignments to so far
// unknown names in comprehensions. We need to consider here more context
// information for correct handling.
self.locals
.get(1)
.expect("no outer scope for non-local")
.set_item(name, value, vm)
.unwrap();
}
}
// pub fn store_cell(&self, vm: &VirtualMachine, name: impl PyName, value: PyObjectRef) {
// // find the innermost outer scope that contains the symbol name
// if let Some(locals) = self
// .locals
// .iter()
// .rev()
// .find(|l| l.contains_key(name.clone(), vm))
// {
// // add to the symbol
// locals.set_item(name, value, vm).unwrap();
// } else {
// // somewhat limited solution -> fallback to the old rustpython strategy
// // and store the next outer scope
// // This case is usually considered as a failure case, but kept for the moment
// // to support the scope propagation for named expression assignments to so far
// // unknown names in comprehensions. We need to consider here more context
// // information for correct handling.
// self.locals
// .get(1)
// .expect("no outer scope for non-local")
// .set_item(name, value, vm)
// .unwrap();
// }
// }
pub fn store_name(&self, vm: &VirtualMachine, key: impl PyName, value: PyObjectRef) {
self.get_locals().set_item(key, value, vm).unwrap();
}
// pub fn store_name(&self, vm: &VirtualMachine, key: impl PyName, value: PyObjectRef) {
// self.get_locals().set_item(key, value, vm).unwrap();
// }
pub fn delete_name(&self, vm: &VirtualMachine, key: impl PyName) -> PyResult {
self.get_locals().del_item(key, vm)
}
// pub fn delete_name(&self, vm: &VirtualMachine, key: impl PyName) -> PyResult {
// self.get_locals().del_item(key, vm)
// }
#[cfg_attr(feature = "flame-it", flame("Scope"))]
/// Load a global name.
pub fn load_global(&self, vm: &VirtualMachine, name: impl PyName) -> Option<PyObjectRef> {
if let Some(value) = self.globals.get_item_option(name.clone(), vm).unwrap() {
Some(value)
} else {
vm.get_attribute(vm.builtins.clone(), name).ok()
}
}
// #[cfg_attr(feature = "flame-it", flame("Scope"))]
// /// Load a global name.
// pub fn load_global(&self, vm: &VirtualMachine, name: impl PyName) -> Option<PyObjectRef> {
// if let Some(value) = self.globals.get_item_option(name.clone(), vm).unwrap() {
// Some(value)
// } else {
// vm.get_attribute(vm.builtins.clone(), name).ok()
// }
// }
pub fn store_global(&self, vm: &VirtualMachine, name: impl PyName, value: PyObjectRef) {
self.globals.set_item(name, value, vm).unwrap();
}
// pub fn store_global(&self, vm: &VirtualMachine, name: impl PyName, value: PyObjectRef) {
// self.globals.set_item(name, value, vm).unwrap();
// }
}
mod sealed {
+2 -2
View File
@@ -205,7 +205,7 @@ mod decl {
#[pymethod(name = "is_nonlocal")]
fn is_nonlocal(&self) -> bool {
matches!(self.symbol.scope, SymbolScope::Nonlocal)
matches!(self.symbol.scope, SymbolScope::Free)
}
#[pymethod(name = "is_referenced")]
@@ -225,7 +225,7 @@ mod decl {
#[pymethod(name = "is_free")]
fn is_free(&self) -> bool {
self.symbol.is_free
matches!(self.symbol.scope, SymbolScope::Free)
}
#[pymethod(name = "is_namespace")]
+13 -13
View File
@@ -448,7 +448,7 @@ impl VirtualMachine {
}
pub fn run_code_obj(&self, code: PyCodeRef, scope: Scope) -> PyResult {
let frame = Frame::new(code, scope, self).into_ref(self);
let frame = Frame::new(code, scope, &[], self).into_ref(self);
self.run_frame_full(frame)
}
@@ -495,11 +495,18 @@ impl VirtualMachine {
}
}
pub fn current_scope(&self) -> Ref<Scope> {
pub fn current_locals(&self) -> Ref<PyDictRef> {
let frame = self
.current_frame()
.expect("called current_scope but no frames on the stack");
Ref::map(frame, |f| &f.scope)
.expect("called current_locals but no frames on the stack");
Ref::map(frame, |f| f.locals(self))
}
pub fn current_globals(&self) -> Ref<PyDictRef> {
let frame = self
.current_frame()
.expect("called current_globals but no frames on the stack");
Ref::map(frame, |f| &f.globals)
}
pub fn try_class(&self, module: &str, class: &str) -> PyResult<PyTypeRef> {
@@ -794,10 +801,6 @@ impl VirtualMachine {
obj.unwrap_or_else(|| self.ctx.none())
}
pub fn get_locals(&self) -> PyDictRef {
self.current_scope().get_locals().clone()
}
// Container of the virtual machine state:
pub fn to_str(&self, obj: &PyObjectRef) -> PyResult<PyStrRef> {
if obj.class().is(&self.ctx.types.str_type) {
@@ -872,12 +875,9 @@ impl VirtualMachine {
})?;
let (locals, globals) = if let Some(frame) = self.current_frame() {
(
frame.scope.get_locals().clone().into_object(),
frame.scope.globals.clone().into_object(),
)
(Some(frame.locals.clone()), Some(frame.globals.clone()))
} else {
(self.ctx.none(), self.ctx.none())
(None, None)
};
let from_list = self
.ctx