fixed scoping issue for nonlocals - now also indirect outer scopes are supported

This commit is contained in:
TheAnyKey committed 2020-06-01 22:47:28 +00:00
1 parent fd2e0aa41e
commit 1773d2bc14
3 files changed
+41 -16

No files matched your search

+5 -6
View File
@@ -166,7 +166,7 @@ class NamedExpressionInvalidTest(unittest.TestCase):
with self.assertRaisesRegex(SyntaxError, msg):
exec(f"lambda: {code}", {}) # Function scope
@unittest.expectedFailure # TODO RustPython
@unittest.expectedFailure # TODO RustPythonskip
def test_named_expression_invalid_comprehension_iterable_expression(self):
cases = [
("Top level", "[i for i in (i := range(5))]"),
@@ -394,21 +394,21 @@ print(a)"""
self.assertEqual(b, [1, 1])
self.assertEqual(a, 1)
# the following test is not from CPyrgon and just as refernce for a common scoping problem of RustPython
@unittest.skip # needs skipping due to weired behaviour
# the following test is not from CPython and just as refernce for a common scoping problem of RustPython
def test_named_expression_scop_10_rp_scope_prob(self):
def foo():
rr=0
rr=36
def foo0():
nonlocal rr
def foo1():
nonlocal rr
self.assertEqual(rr,36)
rr+=42
foo1()
foo0()
return rr
self.assertEqual(foo(), 42)
self.assertEqual(foo(), 78)
def test_named_expression_scope_11(self):
@@ -550,7 +550,6 @@ spam()"""
f()
self.assertEqual(GLOBAL_VAR, None)
@unittest.expectedFailure # TODO RustPython
def test_named_expression_nonlocal_scope(self):
sentinel = object()
def f():
+13 -5
View File
@@ -221,11 +221,19 @@ impl<'a> SymbolTableAnalyzer<'a> {
} else {
match symbol.scope {
SymbolScope::Nonlocal => {
// check if name is defined in parent table!
let parent_symbol_table = self.tables.last();
if let Some((symbols, _)) = parent_symbol_table {
let scope_depth = self.tables.len();
if !symbols.contains_key(&symbol.name) || scope_depth < 2 {
let scope_depth = self.tables.len();
if scope_depth > 0 {
// check if the name is already defined in any outer scope
// therefore
if scope_depth < 2
|| !self
.tables
.iter()
.skip(1) // omit the global scope as it is not non-local
.rev() // revert the order for better performance
.any(|t| t.0.contains_key(&symbol.name))
// true when any of symbol tables contains the name -> then negate
{
return Err(SymbolTableError {
error: format!("no binding for nonlocal '{}' found", symbol.name),
location: Default::default(),
+23 -5
View File
@@ -113,11 +113,29 @@ impl NameProtocol for Scope {
}
fn store_cell(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef) {
self.locals
.get(1)
.expect("no outer scope for non-local")
.set_item(name, value, vm)
.unwrap();
// find the innermost outer scope that contains the symbol name
if let Some(locals) = self
.locals
.iter()
.rev()
.filter(|l| l.contains_key(name, vm))
.nth(0)
{
// 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();
}
}
fn store_name(&self, vm: &VirtualMachine, key: &str, value: PyObjectRef) {