Skip to content

Commit c5ce1bb

Browse files
authored
Merge pull request RustPython#1341 from RustPython/coolreader18/method-scopes
Fix class vs method scopes
2 parents ec1d30b + 51934e0 commit c5ce1bb

File tree

2 files changed

+44
-21
lines changed

2 files changed

+44
-21
lines changed

compiler/src/symboltable.rs

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl SymbolTable {
6161
}
6262
}
6363

64-
#[derive(Clone)]
64+
#[derive(Clone, Copy, PartialEq)]
6565
pub enum SymbolTableType {
6666
Module,
6767
Class,
@@ -179,24 +179,24 @@ fn analyze_symbol_table(symbol_table: &mut SymbolTable) -> SymbolTableResult {
179179
/// build symbol table structure. It will mark variables
180180
/// as local variables for example.
181181
#[derive(Default)]
182-
struct SymbolTableAnalyzer {
183-
tables: Vec<SymbolTable>,
182+
struct SymbolTableAnalyzer<'a> {
183+
tables: Vec<(&'a mut IndexMap<String, Symbol>, SymbolTableType)>,
184184
}
185185

186-
impl SymbolTableAnalyzer {
187-
fn analyze_symbol_table(&mut self, symbol_table: &mut SymbolTable) -> SymbolTableResult {
188-
// Store a copy to determine the parent.
189-
// TODO: this should be improved to resolve this clone action.
190-
self.tables.push(symbol_table.clone());
186+
impl<'a> SymbolTableAnalyzer<'a> {
187+
fn analyze_symbol_table(&mut self, symbol_table: &'a mut SymbolTable) -> SymbolTableResult {
188+
let symbols = &mut symbol_table.symbols;
189+
let sub_tables = &mut symbol_table.sub_tables;
191190

191+
self.tables.push((symbols, symbol_table.typ));
192192
// Analyze sub scopes:
193-
for sub_table in &mut symbol_table.sub_tables {
193+
for sub_table in sub_tables {
194194
self.analyze_symbol_table(sub_table)?;
195195
}
196-
self.tables.pop();
196+
let (symbols, _) = self.tables.pop().unwrap();
197197

198198
// Analyze symbols:
199-
for symbol in symbol_table.symbols.values_mut() {
199+
for symbol in symbols.values_mut() {
200200
self.analyze_symbol(symbol)?;
201201
}
202202

@@ -207,11 +207,11 @@ impl SymbolTableAnalyzer {
207207
match symbol.scope {
208208
SymbolScope::Nonlocal => {
209209
// check if name is defined in parent table!
210-
let parent_symbol_table: Option<&SymbolTable> = self.tables.last();
210+
let parent_symbol_table = self.tables.last();
211211
// symbol.table.borrow().parent.clone();
212212

213-
if let Some(table) = parent_symbol_table {
214-
if !table.symbols.contains_key(&symbol.name) {
213+
if let Some((symbols, _)) = parent_symbol_table {
214+
if !symbols.contains_key(&symbol.name) {
215215
return Err(SymbolTableError {
216216
error: format!("no binding for nonlocal '{}' found", symbol.name),
217217
location: Default::default(),
@@ -241,12 +241,10 @@ impl SymbolTableAnalyzer {
241241
} else {
242242
// Interesting stuff about the __class__ variable:
243243
// https://docs.python.org/3/reference/datamodel.html?highlight=__class__#creating-the-class-object
244-
let found_in_outer_scope = (symbol.name == "__class__")
245-
|| self
246-
.tables
247-
.iter()
248-
.skip(1)
249-
.any(|t| t.symbols.contains_key(&symbol.name));
244+
let found_in_outer_scope = symbol.name == "__class__"
245+
|| self.tables.iter().skip(1).any(|(symbols, typ)| {
246+
*typ != SymbolTableType::Class && symbols.contains_key(&symbol.name)
247+
});
250248

251249
if found_in_outer_scope {
252250
// Symbol is in some outer scope.
@@ -387,7 +385,6 @@ impl SymbolTableBuilder {
387385
keywords,
388386
decorator_list,
389387
} => {
390-
self.register_name(name, SymbolUsage::Assigned)?;
391388
self.enter_scope(name, SymbolTableType::Class, statement.location.row());
392389
self.scan_statements(body)?;
393390
self.leave_scope();
@@ -396,6 +393,7 @@ impl SymbolTableBuilder {
396393
self.scan_expression(&keyword.value, &ExpressionContext::Load)?;
397394
}
398395
self.scan_expressions(decorator_list, &ExpressionContext::Load)?;
396+
self.register_name(name, SymbolUsage::Assigned)?;
399397
}
400398
Expression { expression } => {
401399
self.scan_expression(expression, &ExpressionContext::Load)?

tests/snippets/class.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,28 @@ class T5(int):
164164
assert str(super(int, T5(5))) == "<super: <class 'int'>, <T5 object>>"
165165

166166
#assert str(super(type, None)) == "<super: <class 'type'>, NULL>"
167+
168+
a = 1
169+
class A:
170+
a = 2
171+
def b():
172+
assert a == 1
173+
b()
174+
assert a == 2
175+
A.b()
176+
177+
# TODO: uncomment once free vars/cells are working
178+
# The symboltable sees that b() is referring to a in the nested scope,
179+
# so it marks it as non local. When it's executed, it walks up the scopes
180+
# and still finds the a from the class scope.
181+
# a = 1
182+
# def nested_scope():
183+
# a = 2
184+
# class A:
185+
# a = 3
186+
# def b():
187+
# assert a == 2
188+
# b()
189+
# assert a == 3
190+
# A.b()
191+
# nested_scope()

0 commit comments

Comments
 (0)