Skip to content

Commit 1773d2b

Browse files
committed
fixed scoping issue for nonlocals - now also indirect outer scopes are supported
1 parent fd2e0aa commit 1773d2b

File tree

3 files changed

+41
-16
lines changed

3 files changed

+41
-16
lines changed

Lib/test/test_named_expression.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def test_named_expression_invalid_rebinding_comprehension_inner_loop(self):
166166
with self.assertRaisesRegex(SyntaxError, msg):
167167
exec(f"lambda: {code}", {}) # Function scope
168168

169-
@unittest.expectedFailure # TODO RustPython
169+
@unittest.expectedFailure # TODO RustPythonskip
170170
def test_named_expression_invalid_comprehension_iterable_expression(self):
171171
cases = [
172172
("Top level", "[i for i in (i := range(5))]"),
@@ -394,21 +394,21 @@ def test_named_expression_scope_10(self):
394394
self.assertEqual(b, [1, 1])
395395
self.assertEqual(a, 1)
396396

397-
# the following test is not from CPyrgon and just as refernce for a common scoping problem of RustPython
398-
@unittest.skip # needs skipping due to weired behaviour
397+
# the following test is not from CPython and just as refernce for a common scoping problem of RustPython
399398
def test_named_expression_scop_10_rp_scope_prob(self):
400399
def foo():
401-
rr=0
400+
rr=36
402401
def foo0():
403402
nonlocal rr
404403
def foo1():
405404
nonlocal rr
405+
self.assertEqual(rr,36)
406406
rr+=42
407407
foo1()
408408
foo0()
409409
return rr
410410

411-
self.assertEqual(foo(), 42)
411+
self.assertEqual(foo(), 78)
412412

413413

414414
def test_named_expression_scope_11(self):
@@ -550,7 +550,6 @@ def f():
550550
f()
551551
self.assertEqual(GLOBAL_VAR, None)
552552

553-
@unittest.expectedFailure # TODO RustPython
554553
def test_named_expression_nonlocal_scope(self):
555554
sentinel = object()
556555
def f():

compiler/src/symboltable.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,19 @@ impl<'a> SymbolTableAnalyzer<'a> {
221221
} else {
222222
match symbol.scope {
223223
SymbolScope::Nonlocal => {
224-
// check if name is defined in parent table!
225-
let parent_symbol_table = self.tables.last();
226-
if let Some((symbols, _)) = parent_symbol_table {
227-
let scope_depth = self.tables.len();
228-
if !symbols.contains_key(&symbol.name) || scope_depth < 2 {
224+
let scope_depth = self.tables.len();
225+
if scope_depth > 0 {
226+
// check if the name is already defined in any outer scope
227+
// therefore
228+
if scope_depth < 2
229+
|| !self
230+
.tables
231+
.iter()
232+
.skip(1) // omit the global scope as it is not non-local
233+
.rev() // revert the order for better performance
234+
.any(|t| t.0.contains_key(&symbol.name))
235+
// true when any of symbol tables contains the name -> then negate
236+
{
229237
return Err(SymbolTableError {
230238
error: format!("no binding for nonlocal '{}' found", symbol.name),
231239
location: Default::default(),

vm/src/scope.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,29 @@ impl NameProtocol for Scope {
113113
}
114114

115115
fn store_cell(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef) {
116-
self.locals
117-
.get(1)
118-
.expect("no outer scope for non-local")
119-
.set_item(name, value, vm)
120-
.unwrap();
116+
// find the innermost outer scope that contains the symbol name
117+
if let Some(locals) = self
118+
.locals
119+
.iter()
120+
.rev()
121+
.filter(|l| l.contains_key(name, vm))
122+
.nth(0)
123+
{
124+
// add to the symbol
125+
locals.set_item(name, value, vm).unwrap();
126+
} else {
127+
// somewhat limited solution -> fallback to the old rustpython strategy
128+
// and store the next outer scope
129+
// This case is usually considered as a failure case, but kept for the moment
130+
// to support the scope propagation for named expression assignments to so far
131+
// unknown names in comprehensions. We need to consider here more context
132+
// information for correct handling.
133+
self.locals
134+
.get(1)
135+
.expect("no outer scope for non-local")
136+
.set_item(name, value, vm)
137+
.unwrap();
138+
}
121139
}
122140

123141
fn store_name(&self, vm: &VirtualMachine, key: &str, value: PyObjectRef) {

0 commit comments

Comments
 (0)