Skip to content

Commit ce8ea80

Browse files
committed
Add check for nonlocal on top level.
1 parent adb6632 commit ce8ea80

File tree

4 files changed

+89
-13
lines changed

4 files changed

+89
-13
lines changed

tests/snippets/global_nonlocal.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def b():
1111
b()
1212
assert a == 4
1313

14+
1415
def x():
1516
def y():
1617
nonlocal b
@@ -21,3 +22,37 @@ def y():
2122

2223
res = x()
2324
assert res == 3, str(res)
25+
26+
# Invalid syntax:
27+
src = """
28+
b = 2
29+
global b
30+
"""
31+
32+
try:
33+
exec(src)
34+
except Exception as ex:
35+
# print(ex)
36+
pass
37+
else:
38+
raise RuntimeError('Must raise')
39+
40+
# Invalid syntax:
41+
42+
src = """
43+
nonlocal c
44+
"""
45+
46+
try:
47+
exec(src)
48+
except Exception as ex:
49+
# print(ex)
50+
pass
51+
else:
52+
raise RuntimeError('Must raise')
53+
54+
55+
# class X:
56+
# nonlocal c
57+
# c = 2
58+

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: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,27 @@ Then the compiler can use the symbol table to generate proper
55
load and store instructions for names.
66
*/
77

8+
use crate::error::{CompileError, CompileErrorType};
89
use rustpython_parser::ast;
10+
use rustpython_parser::lexer::Location;
911
use std::collections::HashMap;
1012

11-
pub fn make_symbol_table(program: &ast::Program) -> SymbolScope {
13+
pub fn make_symbol_table(program: &ast::Program) -> Result<SymbolScope, SymbolTableError> {
1214
let mut builder = SymbolTableBuilder::new();
1315
builder.enter_scope();
14-
builder.scan_program(program).unwrap();
16+
builder.scan_program(program)?;
1517
assert!(builder.scopes.len() == 1);
16-
builder.scopes.pop().unwrap()
18+
Ok(builder.scopes.pop().unwrap())
1719
}
1820

19-
pub fn statements_to_symbol_table(statements: &[ast::LocatedStatement]) -> SymbolScope {
21+
pub fn statements_to_symbol_table(
22+
statements: &[ast::LocatedStatement],
23+
) -> Result<SymbolScope, SymbolTableError> {
2024
let mut builder = SymbolTableBuilder::new();
2125
builder.enter_scope();
22-
builder.scan_statements(statements).unwrap();
26+
builder.scan_statements(statements)?;
2327
assert!(builder.scopes.len() == 1);
24-
builder.scopes.pop().unwrap()
28+
Ok(builder.scopes.pop().unwrap())
2529
}
2630

2731
#[derive(Debug)]
@@ -43,7 +47,22 @@ pub struct SymbolScope {
4347
pub sub_scopes: Vec<SymbolScope>,
4448
}
4549

46-
type SymbolTableResult = Result<(), String>;
50+
#[derive(Debug)]
51+
pub struct SymbolTableError {
52+
error: String,
53+
location: Location,
54+
}
55+
56+
impl From<SymbolTableError> for CompileError {
57+
fn from(error: SymbolTableError) -> Self {
58+
CompileError {
59+
error: CompileErrorType::SyntaxError(error.error),
60+
location: error.location,
61+
}
62+
}
63+
}
64+
65+
type SymbolTableResult = Result<(), SymbolTableError>;
4766

4867
impl SymbolScope {
4968
pub fn new() -> Self {
@@ -85,7 +104,7 @@ impl SymbolTableBuilder {
85104
self.scopes.push(scope);
86105
}
87106

88-
pub fn leave_scope(&mut self) {
107+
fn leave_scope(&mut self) {
89108
// Pop scope and add to subscopes of parent scope.
90109
let scope = self.scopes.pop().unwrap();
91110
self.scopes.last_mut().unwrap().sub_scopes.push(scope);
@@ -445,17 +464,37 @@ impl SymbolTableBuilder {
445464
}
446465

447466
fn register_name(&mut self, name: &str, role: SymbolRole) -> SymbolTableResult {
467+
let scope_depth = self.scopes.len();
448468
let current_scope = self.scopes.last_mut().unwrap();
469+
let location = Default::default();
449470
if let Some(_old_role) = current_scope.symbols.get(name) {
450471
// Role already set..
451472
// debug!("TODO: {:?}", old_role);
452473
match role {
453-
SymbolRole::Global => return Err("global must appear first".to_string()),
474+
SymbolRole::Global => {
475+
return Err(SymbolTableError {
476+
error: format!("name '{}' is used prior to global declaration", name),
477+
location,
478+
})
479+
}
454480
_ => {
455481
// Ok?
456482
}
457483
}
458484
} else {
485+
match role {
486+
SymbolRole::Nonlocal => {
487+
if scope_depth < 2 {
488+
return Err(SymbolTableError {
489+
error: format!("name '{}' is used prior to global declaration", name),
490+
location,
491+
});
492+
}
493+
}
494+
_ => {
495+
// Ok!
496+
}
497+
}
459498
current_scope.symbols.insert(name.to_string(), role);
460499
}
461500
Ok(())

0 commit comments

Comments
 (0)