Skip to content

Commit 05b6f3d

Browse files
committed
Implement block scoping rules
1 parent 2bf3390 commit 05b6f3d

File tree

9 files changed

+235
-74
lines changed

9 files changed

+235
-74
lines changed

compiler/tests/compile_errors.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ use std::fs;
99
#[rstest(
1010
fixture_file,
1111
error,
12+
case(
13+
"not_in_scope.fe",
14+
"[Str(\"semantic error: UndefinedValue { value: \\\"y\\\" }\")]"
15+
),
16+
case(
17+
"not_in_scope_2.fe",
18+
"[Str(\"semantic error: UndefinedValue { value: \\\"y\\\" }\")]"
19+
),
1220
case("mismatch_return_type.fe", "[Str(\"semantic error: TypeError\")]"),
1321
case("unexpected_return.fe", "[Str(\"semantic error: TypeError\")]"),
1422
case("missing_return.fe", "[Str(\"semantic error: MissingReturn\")]"),
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
contract Foo:
2+
3+
pub def bar() -> u256:
4+
if true:
5+
y: u256 = 1
6+
else:
7+
y: u256 = 1
8+
return y
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
contract Foo:
2+
3+
pub def bar() -> u256:
4+
i: u256 = 0
5+
while i < 1:
6+
i = 1
7+
y: u256 = 1
8+
return y
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
contract Foo:
2+
3+
pub def bar() -> u256:
4+
if true:
5+
y: u256 = 1
6+
return y
7+
else:
8+
y: u256 = 1
9+
return y

semantics/src/namespace/scopes.rs

Lines changed: 139 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub enum ContractDef {
3434
}
3535

3636
#[derive(Clone, Debug, PartialEq)]
37-
pub enum FunctionDef {
37+
pub enum BlockDef {
3838
Base(Base),
3939
Array(Array),
4040
}
@@ -53,25 +53,31 @@ pub struct ContractScope {
5353
}
5454

5555
#[derive(Clone, Debug, PartialEq)]
56-
pub struct FunctionScope {
56+
pub struct BlockScope {
5757
pub span: Span,
58-
pub parent: Shared<ContractScope>,
59-
pub defs: HashMap<String, FunctionDef>,
58+
pub parent: BlockScopeParent,
59+
pub defs: HashMap<String, BlockDef>,
6060
}
6161

6262
#[allow(dead_code)]
6363
pub enum Scope {
6464
Module(Shared<ModuleScope>),
6565
Contract(Shared<ContractScope>),
66-
Function(Shared<FunctionScope>),
66+
Block(Shared<BlockScope>),
67+
}
68+
69+
#[derive(Clone, Debug, PartialEq)]
70+
pub enum BlockScopeParent {
71+
Contract(Shared<ContractScope>),
72+
Block(Shared<BlockScope>),
6773
}
6874

6975
impl Scope {
7076
pub fn module_scope(&self) -> Shared<ModuleScope> {
7177
match self {
7278
Scope::Module(scope) => Rc::clone(scope),
7379
Scope::Contract(scope) => Rc::clone(&scope.borrow().parent),
74-
Scope::Function(scope) => Rc::clone(&scope.borrow().parent.borrow().parent),
80+
Scope::Block(scope) => Rc::clone(&scope.borrow().contract_scope().borrow().parent),
7581
}
7682
}
7783
}
@@ -136,9 +142,17 @@ impl ContractScope {
136142
}
137143
}
138144

139-
impl FunctionScope {
140-
pub fn new(span: Span, parent: Shared<ContractScope>) -> Shared<Self> {
141-
Rc::new(RefCell::new(FunctionScope {
145+
impl BlockScope {
146+
pub fn from_contract_scope(span: Span, parent: Shared<ContractScope>) -> Shared<Self> {
147+
BlockScope::new(span, BlockScopeParent::Contract(parent))
148+
}
149+
150+
pub fn from_block_scope(span: Span, parent: Shared<BlockScope>) -> Shared<Self> {
151+
BlockScope::new(span, BlockScopeParent::Block(parent))
152+
}
153+
154+
pub fn new(span: Span, parent: BlockScopeParent) -> Shared<Self> {
155+
Rc::new(RefCell::new(BlockScope {
142156
span,
143157
parent,
144158
defs: HashMap::new(),
@@ -147,26 +161,136 @@ impl FunctionScope {
147161

148162
#[allow(dead_code)]
149163
pub fn module_scope(&self) -> Shared<ModuleScope> {
150-
Rc::clone(&self.parent.borrow().parent)
164+
Rc::clone(&self.contract_scope().borrow().parent)
151165
}
152166

167+
/// Return the contract scope and its immediate block scope child
168+
fn find_scope_boundary(&self) -> (Shared<ContractScope>, Shared<BlockScope>) {
169+
let mut parent = self.parent.clone();
170+
let mut last_block_scope = Rc::new(RefCell::new(self.clone()));
171+
loop {
172+
parent = match parent {
173+
BlockScopeParent::Block(ref scope) => {
174+
last_block_scope = scope.clone();
175+
scope.borrow().parent.clone()
176+
}
177+
BlockScopeParent::Contract(ref scope) => return (scope.clone(), last_block_scope),
178+
}
179+
}
180+
}
181+
182+
/// Return the contract scope that the block scope inherits from
153183
pub fn contract_scope(&self) -> Shared<ContractScope> {
154-
Rc::clone(&self.parent)
184+
let (contract_scope, _) = self.find_scope_boundary();
185+
contract_scope
186+
}
187+
188+
/// Return the block scope that is associated with the function block
189+
pub fn function_scope(&self) -> Shared<BlockScope> {
190+
let (_, function_scope) = self.find_scope_boundary();
191+
function_scope
155192
}
156193

157194
pub fn contract_def(&self, name: String) -> Option<ContractDef> {
158195
self.contract_scope().borrow().def(name)
159196
}
160197

161-
pub fn def(&self, name: String) -> Option<FunctionDef> {
162-
self.defs.get(&name).map(|def| (*def).clone())
198+
/// Lookup definition in current or inherited block scope
199+
pub fn def(&self, name: String) -> Option<BlockDef> {
200+
let block_def = self.defs.get(&name).map(|def| (*def).clone());
201+
if block_def.is_none() {
202+
if let BlockScopeParent::Block(scope) = &self.parent {
203+
scope.borrow().def(name)
204+
} else {
205+
None
206+
}
207+
} else {
208+
block_def
209+
}
163210
}
164211

165212
pub fn add_array(&mut self, name: String, array: Array) {
166-
self.defs.insert(name, FunctionDef::Array(array));
213+
self.defs.insert(name, BlockDef::Array(array));
167214
}
168215

169216
pub fn add_base(&mut self, name: String, base: Base) {
170-
self.defs.insert(name, FunctionDef::Base(base));
217+
self.defs.insert(name, BlockDef::Base(base));
218+
}
219+
}
220+
221+
#[cfg(test)]
222+
mod tests {
223+
use crate::namespace::scopes::{
224+
BlockDef,
225+
BlockScope,
226+
ContractScope,
227+
ModuleScope,
228+
};
229+
use crate::namespace::types::Base;
230+
use fe_parser::span::Span;
231+
232+
#[test]
233+
fn test_scope_resolution_on_first_level_block_scope() {
234+
let module_scope = ModuleScope::new();
235+
let contract_scope = ContractScope::new(module_scope);
236+
let block_scope_1 =
237+
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
238+
assert_eq!(block_scope_1, block_scope_1.borrow().function_scope());
239+
assert_eq!(contract_scope, block_scope_1.borrow().contract_scope());
240+
}
241+
242+
#[test]
243+
fn test_scope_resolution_on_second_level_block_scope() {
244+
let module_scope = ModuleScope::new();
245+
let contract_scope = ContractScope::new(module_scope);
246+
let block_scope_1 =
247+
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
248+
let block_scope_2 = BlockScope::from_block_scope(Span::new(0, 0), block_scope_1.clone());
249+
assert_eq!(block_scope_1, block_scope_2.borrow().function_scope());
250+
assert_eq!(contract_scope, block_scope_2.borrow().contract_scope());
251+
}
252+
253+
#[test]
254+
fn test_1st_level_def_lookup_on_1st_level_block_scope() {
255+
let module_scope = ModuleScope::new();
256+
let contract_scope = ContractScope::new(module_scope);
257+
let block_scope_1 =
258+
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
259+
block_scope_1
260+
.borrow_mut()
261+
.add_base("some_thing".to_string(), Base::Bool);
262+
assert_eq!(
263+
Some(BlockDef::Base(Base::Bool)),
264+
block_scope_1.borrow().def("some_thing".to_string())
265+
);
266+
}
267+
268+
#[test]
269+
fn test_1st_level_def_lookup_on_2nd_level_block_scope() {
270+
let module_scope = ModuleScope::new();
271+
let contract_scope = ContractScope::new(module_scope);
272+
let block_scope_1 =
273+
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
274+
let block_scope_2 = BlockScope::from_block_scope(Span::new(0, 0), block_scope_1.clone());
275+
block_scope_1
276+
.borrow_mut()
277+
.add_base("some_thing".to_string(), Base::Bool);
278+
assert_eq!(
279+
Some(BlockDef::Base(Base::Bool)),
280+
block_scope_2.borrow().def("some_thing".to_string())
281+
);
282+
}
283+
284+
#[test]
285+
fn test_2nd_level_def_lookup_on_1nd_level_block_scope_fails() {
286+
let module_scope = ModuleScope::new();
287+
let contract_scope = ContractScope::new(module_scope);
288+
let block_scope_1 =
289+
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
290+
let block_scope_2 = BlockScope::from_block_scope(Span::new(0, 0), block_scope_1.clone());
291+
block_scope_2
292+
.borrow_mut()
293+
.add_base("some_thing".to_string(), Base::Bool);
294+
assert_eq!(None, block_scope_1.borrow().def("some_thing".to_string()));
171295
}
172296
}

semantics/src/traversal/assignments.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::errors::SemanticError;
22
use crate::namespace::operations;
33
use crate::namespace::scopes::{
4-
FunctionScope,
4+
BlockScope,
55
Shared,
66
};
77
use crate::traversal::expressions;
@@ -14,7 +14,7 @@ use std::rc::Rc;
1414
///
1515
/// e.g. `foo[42] = "bar"`, `self.foo[42] = "bar"`, `foo = 42`
1616
pub fn assign(
17-
scope: Shared<FunctionScope>,
17+
scope: Shared<BlockScope>,
1818
context: Shared<Context>,
1919
stmt: &Spanned<fe::FuncStmt>,
2020
) -> Result<(), SemanticError> {
@@ -44,7 +44,7 @@ pub fn assign(
4444
///
4545
/// e.g. `foo[42] = "bar"`, `self.foo[42] = "bar"`
4646
fn assign_subscript(
47-
scope: Shared<FunctionScope>,
47+
scope: Shared<BlockScope>,
4848
context: Shared<Context>,
4949
target: &Spanned<fe::Expr>,
5050
value: &Spanned<fe::Expr>,
@@ -74,7 +74,7 @@ fn assign_subscript(
7474
///
7575
/// e.g. `foo = 42`
7676
fn assign_name(
77-
scope: Shared<FunctionScope>,
77+
scope: Shared<BlockScope>,
7878
context: Shared<Context>,
7979
target: &Spanned<fe::Expr>,
8080
value: &Spanned<fe::Expr>,
@@ -93,8 +93,8 @@ fn assign_name(
9393
mod tests {
9494
use crate::errors::SemanticError;
9595
use crate::namespace::scopes::{
96+
BlockScope,
9697
ContractScope,
97-
FunctionScope,
9898
ModuleScope,
9999
Shared,
100100
};
@@ -115,7 +115,7 @@ mod tests {
115115
// - self.foobar: Map<u256, u256>
116116
// - foo: u256
117117
// - bar: u256[100]
118-
fn scope() -> Shared<FunctionScope> {
118+
fn scope() -> Shared<BlockScope> {
119119
let module_scope = ModuleScope::new();
120120
let contract_scope = ContractScope::new(module_scope);
121121
contract_scope.borrow_mut().add_map(
@@ -125,7 +125,7 @@ mod tests {
125125
value: Box::new(Type::Base(Base::U256)),
126126
},
127127
);
128-
let function_scope = FunctionScope::new(Span::new(0, 0), contract_scope);
128+
let function_scope = BlockScope::from_contract_scope(Span::new(0, 0), contract_scope);
129129
function_scope
130130
.borrow_mut()
131131
.add_base("foo".to_string(), Base::U256);
@@ -139,7 +139,7 @@ mod tests {
139139
function_scope
140140
}
141141

142-
fn analyze(scope: Shared<FunctionScope>, src: &str) -> Result<Context, SemanticError> {
142+
fn analyze(scope: Shared<BlockScope>, src: &str) -> Result<Context, SemanticError> {
143143
let context = Context::new_shared();
144144
let tokens = parser::get_parse_tokens(src).expect("Couldn't parse expression");
145145
let assignment = &parser::parsers::assign_stmt(&tokens[..])

semantics/src/traversal/declarations.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::errors::SemanticError;
22
use crate::namespace::scopes::{
3-
FunctionScope,
3+
BlockScope,
44
Scope,
55
Shared,
66
};
@@ -16,13 +16,13 @@ use std::rc::Rc;
1616

1717
/// Gather context information for var declarations and check for type errors.
1818
pub fn var_decl(
19-
scope: Shared<FunctionScope>,
19+
scope: Shared<BlockScope>,
2020
context: Shared<Context>,
2121
stmt: &Spanned<fe::FuncStmt>,
2222
) -> Result<(), SemanticError> {
2323
if let fe::FuncStmt::VarDecl { target, typ, value } = &stmt.node {
2424
let name = expressions::expr_name_string(target)?;
25-
let declared_type = types::type_desc_fixed_size(Scope::Function(Rc::clone(&scope)), typ)?;
25+
let declared_type = types::type_desc_fixed_size(Scope::Block(Rc::clone(&scope)), typ)?;
2626
if let Some(value) = value {
2727
let value_attributes =
2828
expressions::expr(Rc::clone(&scope), Rc::clone(&context), value)?;
@@ -48,9 +48,9 @@ pub fn var_decl(
4848
mod tests {
4949
use crate::errors::SemanticError;
5050
use crate::namespace::scopes::{
51+
BlockDef,
52+
BlockScope,
5153
ContractScope,
52-
FunctionDef,
53-
FunctionScope,
5454
ModuleScope,
5555
Shared,
5656
};
@@ -61,13 +61,13 @@ mod tests {
6161
use fe_parser::span::Span;
6262
use std::rc::Rc;
6363

64-
fn scope() -> Shared<FunctionScope> {
64+
fn scope() -> Shared<BlockScope> {
6565
let module_scope = ModuleScope::new();
6666
let contract_scope = ContractScope::new(module_scope);
67-
FunctionScope::new(Span::new(0, 0), contract_scope)
67+
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope)
6868
}
6969

70-
fn analyze(scope: Shared<FunctionScope>, src: &str) -> Result<Context, SemanticError> {
70+
fn analyze(scope: Shared<BlockScope>, src: &str) -> Result<Context, SemanticError> {
7171
let context = Context::new_shared();
7272
let tokens = parser::get_parse_tokens(src).expect("Couldn't parse expression");
7373
let statement = &parser::parsers::vardecl_stmt(&tokens[..])
@@ -89,7 +89,7 @@ mod tests {
8989
assert_eq!(context.expressions.len(), 3);
9090
assert_eq!(
9191
scope.borrow().def("foo".to_string()),
92-
Some(FunctionDef::Base(Base::U256))
92+
Some(BlockDef::Base(Base::U256))
9393
);
9494
}
9595

0 commit comments

Comments
 (0)