Skip to content

Commit 63a71d6

Browse files
authored
Merge pull request RustPython#855 from RustPython/nonlocal-errors
Add check for nonlocal on top level.
2 parents 2c7f3b5 + 8982f56 commit 63a71d6

File tree

4 files changed

+155
-13
lines changed

4 files changed

+155
-13
lines changed

tests/snippets/global_nonlocal.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from testutils import assertRaises
12

23
# Test global and nonlocal funkyness
34

@@ -11,6 +12,7 @@ def b():
1112
b()
1213
assert a == 4
1314

15+
1416
def x():
1517
def y():
1618
nonlocal b
@@ -21,3 +23,38 @@ def y():
2123

2224
res = x()
2325
assert res == 3, str(res)
26+
27+
# Invalid syntax:
28+
src = """
29+
b = 2
30+
global b
31+
"""
32+
33+
with assertRaises(SyntaxError):
34+
exec(src)
35+
36+
# Invalid syntax:
37+
src = """
38+
nonlocal c
39+
"""
40+
41+
with assertRaises(SyntaxError):
42+
exec(src)
43+
44+
45+
# Invalid syntax:
46+
src = """
47+
def f():
48+
def x():
49+
nonlocal c
50+
c = 2
51+
"""
52+
53+
with assertRaises(SyntaxError):
54+
exec(src)
55+
56+
57+
# class X:
58+
# nonlocal c
59+
# c = 2
60+

vm/src/compile.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,17 @@ pub fn compile(
4141
match mode {
4242
Mode::Exec => {
4343
let ast = parser::parse_program(source)?;
44-
let symbol_table = make_symbol_table(&ast);
44+
let symbol_table = make_symbol_table(&ast)?;
4545
compiler.compile_program(&ast, symbol_table)
4646
}
4747
Mode::Eval => {
4848
let statement = parser::parse_statement(source)?;
49-
let symbol_table = statements_to_symbol_table(&statement);
49+
let symbol_table = statements_to_symbol_table(&statement)?;
5050
compiler.compile_statement_eval(&statement, symbol_table)
5151
}
5252
Mode::Single => {
5353
let ast = parser::parse_program(source)?;
54-
let symbol_table = make_symbol_table(&ast);
54+
let symbol_table = make_symbol_table(&ast)?;
5555
compiler.compile_program_single(&ast, symbol_table)
5656
}
5757
}?;
@@ -1776,7 +1776,7 @@ mod tests {
17761776
compiler.source_path = Some("source_path".to_string());
17771777
compiler.push_new_code_object("<module>".to_string());
17781778
let ast = parser::parse_program(&source.to_string()).unwrap();
1779-
let symbol_scope = make_symbol_table(&ast);
1779+
let symbol_scope = make_symbol_table(&ast).unwrap();
17801780
compiler.compile_program(&ast, symbol_scope).unwrap();
17811781
compiler.pop_code_object()
17821782
}

vm/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub enum CompileErrorType {
2929
ExpectExpr,
3030
/// Parser error
3131
Parse(ParseError),
32+
SyntaxError(String),
3233
/// Multiple `*` detected
3334
StarArgs,
3435
/// Break statement outside of loop.
@@ -46,6 +47,7 @@ impl fmt::Display for CompileError {
4647
CompileErrorType::Delete(target) => write!(f, "can't delete {}", target),
4748
CompileErrorType::ExpectExpr => write!(f, "Expecting expression, got statement"),
4849
CompileErrorType::Parse(err) => write!(f, "{}", err),
50+
CompileErrorType::SyntaxError(err) => write!(f, "{}", err),
4951
CompileErrorType::StarArgs => write!(f, "Two starred expressions in assignment"),
5052
CompileErrorType::InvalidBreak => write!(f, "'break' outside loop"),
5153
CompileErrorType::InvalidContinue => write!(f, "'continue' outside loop"),

vm/src/symboltable.rs

Lines changed: 112 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,35 @@
33
This ensures that global and nonlocal keywords are picked up.
44
Then the compiler can use the symbol table to generate proper
55
load and store instructions for names.
6+
7+
Inspirational file: https://github.com/python/cpython/blob/master/Python/symtable.c
68
*/
79

10+
use crate::error::{CompileError, CompileErrorType};
811
use rustpython_parser::ast;
12+
use rustpython_parser::lexer::Location;
913
use std::collections::HashMap;
1014

11-
pub fn make_symbol_table(program: &ast::Program) -> SymbolScope {
15+
pub fn make_symbol_table(program: &ast::Program) -> Result<SymbolScope, SymbolTableError> {
1216
let mut builder = SymbolTableBuilder::new();
1317
builder.enter_scope();
14-
builder.scan_program(program).unwrap();
18+
builder.scan_program(program)?;
1519
assert!(builder.scopes.len() == 1);
16-
builder.scopes.pop().unwrap()
20+
let symbol_table = builder.scopes.pop().unwrap();
21+
analyze_symbol_table(&symbol_table, None)?;
22+
Ok(symbol_table)
1723
}
1824

19-
pub fn statements_to_symbol_table(statements: &[ast::LocatedStatement]) -> SymbolScope {
25+
pub fn statements_to_symbol_table(
26+
statements: &[ast::LocatedStatement],
27+
) -> Result<SymbolScope, SymbolTableError> {
2028
let mut builder = SymbolTableBuilder::new();
2129
builder.enter_scope();
22-
builder.scan_statements(statements).unwrap();
30+
builder.scan_statements(statements)?;
2331
assert!(builder.scopes.len() == 1);
24-
builder.scopes.pop().unwrap()
32+
let symbol_table = builder.scopes.pop().unwrap();
33+
analyze_symbol_table(&symbol_table, None)?;
34+
Ok(symbol_table)
2535
}
2636

2737
#[derive(Debug)]
@@ -43,7 +53,22 @@ pub struct SymbolScope {
4353
pub sub_scopes: Vec<SymbolScope>,
4454
}
4555

46-
type SymbolTableResult = Result<(), String>;
56+
#[derive(Debug)]
57+
pub struct SymbolTableError {
58+
error: String,
59+
location: Location,
60+
}
61+
62+
impl From<SymbolTableError> for CompileError {
63+
fn from(error: SymbolTableError) -> Self {
64+
CompileError {
65+
error: CompileErrorType::SyntaxError(error.error),
66+
location: error.location,
67+
}
68+
}
69+
}
70+
71+
type SymbolTableResult = Result<(), SymbolTableError>;
4772

4873
impl SymbolScope {
4974
pub fn new() -> Self {
@@ -69,6 +94,58 @@ impl std::fmt::Debug for SymbolScope {
6994
}
7095
}
7196

97+
/* Perform some sort of analysis on nonlocals, globals etc..
98+
See also: https://github.com/python/cpython/blob/master/Python/symtable.c#L410
99+
*/
100+
fn analyze_symbol_table(
101+
symbol_scope: &SymbolScope,
102+
parent_symbol_scope: Option<&SymbolScope>,
103+
) -> SymbolTableResult {
104+
// println!("Analyzing {:?}, parent={:?} symbols={:?}", symbol_scope, parent_symbol_scope, symbol_scope.symbols);
105+
// Analyze sub scopes:
106+
for sub_scope in &symbol_scope.sub_scopes {
107+
analyze_symbol_table(&sub_scope, Some(symbol_scope))?;
108+
}
109+
110+
// Analyze symbols:
111+
for (symbol_name, symbol_role) in &symbol_scope.symbols {
112+
analyze_symbol(symbol_name, symbol_role, parent_symbol_scope)?;
113+
}
114+
115+
Ok(())
116+
}
117+
118+
fn analyze_symbol(
119+
symbol_name: &str,
120+
symbol_role: &SymbolRole,
121+
parent_symbol_scope: Option<&SymbolScope>,
122+
) -> SymbolTableResult {
123+
match symbol_role {
124+
SymbolRole::Nonlocal => {
125+
// check if name is defined in parent scope!
126+
if let Some(parent_symbol_scope) = parent_symbol_scope {
127+
if !parent_symbol_scope.symbols.contains_key(symbol_name) {
128+
return Err(SymbolTableError {
129+
error: format!("no binding for nonlocal '{}' found", symbol_name),
130+
location: Default::default(),
131+
});
132+
}
133+
} else {
134+
return Err(SymbolTableError {
135+
error: format!(
136+
"nonlocal {} defined at place without an enclosing scope",
137+
symbol_name
138+
),
139+
location: Default::default(),
140+
});
141+
}
142+
}
143+
// TODO: add more checks for globals
144+
_ => {}
145+
}
146+
Ok(())
147+
}
148+
72149
pub struct SymbolTableBuilder {
73150
// Scope stack.
74151
pub scopes: Vec<SymbolScope>,
@@ -85,7 +162,7 @@ impl SymbolTableBuilder {
85162
self.scopes.push(scope);
86163
}
87164

88-
pub fn leave_scope(&mut self) {
165+
fn leave_scope(&mut self) {
89166
// Pop scope and add to subscopes of parent scope.
90167
let scope = self.scopes.pop().unwrap();
91168
self.scopes.last_mut().unwrap().sub_scopes.push(scope);
@@ -445,17 +522,43 @@ impl SymbolTableBuilder {
445522
}
446523

447524
fn register_name(&mut self, name: &str, role: SymbolRole) -> SymbolTableResult {
525+
let scope_depth = self.scopes.len();
448526
let current_scope = self.scopes.last_mut().unwrap();
527+
let location = Default::default();
449528
if let Some(_old_role) = current_scope.symbols.get(name) {
450529
// Role already set..
451530
// debug!("TODO: {:?}", old_role);
452531
match role {
453-
SymbolRole::Global => return Err("global must appear first".to_string()),
532+
SymbolRole::Global => {
533+
return Err(SymbolTableError {
534+
error: format!("name '{}' is used prior to global declaration", name),
535+
location,
536+
})
537+
}
538+
SymbolRole::Nonlocal => {
539+
return Err(SymbolTableError {
540+
error: format!("name '{}' is used prior to nonlocal declaration", name),
541+
location,
542+
})
543+
}
454544
_ => {
455545
// Ok?
456546
}
457547
}
458548
} else {
549+
match role {
550+
SymbolRole::Nonlocal => {
551+
if scope_depth < 2 {
552+
return Err(SymbolTableError {
553+
error: format!("cannot define nonlocal '{}' at top level.", name),
554+
location,
555+
});
556+
}
557+
}
558+
_ => {
559+
// Ok!
560+
}
561+
}
459562
current_scope.symbols.insert(name.to_string(), role);
460563
}
461564
Ok(())

0 commit comments

Comments
 (0)