Skip to content

Commit

Permalink
Implement block scoping rules
Browse files Browse the repository at this point in the history
  • Loading branch information
cburgdorf committed Nov 17, 2020
1 parent 2bf3390 commit 05b6f3d
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 74 deletions.
8 changes: 8 additions & 0 deletions compiler/tests/compile_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ use std::fs;
#[rstest(
fixture_file,
error,
case(
"not_in_scope.fe",
"[Str(\"semantic error: UndefinedValue { value: \\\"y\\\" }\")]"
),
case(
"not_in_scope_2.fe",
"[Str(\"semantic error: UndefinedValue { value: \\\"y\\\" }\")]"
),
case("mismatch_return_type.fe", "[Str(\"semantic error: TypeError\")]"),
case("unexpected_return.fe", "[Str(\"semantic error: TypeError\")]"),
case("missing_return.fe", "[Str(\"semantic error: MissingReturn\")]"),
Expand Down
8 changes: 8 additions & 0 deletions compiler/tests/fixtures/compile_errors/not_in_scope.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
contract Foo:

pub def bar() -> u256:
if true:
y: u256 = 1
else:
y: u256 = 1
return y
8 changes: 8 additions & 0 deletions compiler/tests/fixtures/compile_errors/not_in_scope_2.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
contract Foo:

pub def bar() -> u256:
i: u256 = 0
while i < 1:
i = 1
y: u256 = 1
return y
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
contract Foo:

pub def bar() -> u256:
if true:
y: u256 = 1
return y
else:
y: u256 = 1
return y
154 changes: 139 additions & 15 deletions semantics/src/namespace/scopes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub enum ContractDef {
}

#[derive(Clone, Debug, PartialEq)]
pub enum FunctionDef {
pub enum BlockDef {
Base(Base),
Array(Array),
}
Expand All @@ -53,25 +53,31 @@ pub struct ContractScope {
}

#[derive(Clone, Debug, PartialEq)]
pub struct FunctionScope {
pub struct BlockScope {
pub span: Span,
pub parent: Shared<ContractScope>,
pub defs: HashMap<String, FunctionDef>,
pub parent: BlockScopeParent,
pub defs: HashMap<String, BlockDef>,
}

#[allow(dead_code)]
pub enum Scope {
Module(Shared<ModuleScope>),
Contract(Shared<ContractScope>),
Function(Shared<FunctionScope>),
Block(Shared<BlockScope>),
}

#[derive(Clone, Debug, PartialEq)]
pub enum BlockScopeParent {
Contract(Shared<ContractScope>),
Block(Shared<BlockScope>),
}

impl Scope {
pub fn module_scope(&self) -> Shared<ModuleScope> {
match self {
Scope::Module(scope) => Rc::clone(scope),
Scope::Contract(scope) => Rc::clone(&scope.borrow().parent),
Scope::Function(scope) => Rc::clone(&scope.borrow().parent.borrow().parent),
Scope::Block(scope) => Rc::clone(&scope.borrow().contract_scope().borrow().parent),
}
}
}
Expand Down Expand Up @@ -136,9 +142,17 @@ impl ContractScope {
}
}

impl FunctionScope {
pub fn new(span: Span, parent: Shared<ContractScope>) -> Shared<Self> {
Rc::new(RefCell::new(FunctionScope {
impl BlockScope {
pub fn from_contract_scope(span: Span, parent: Shared<ContractScope>) -> Shared<Self> {
BlockScope::new(span, BlockScopeParent::Contract(parent))
}

pub fn from_block_scope(span: Span, parent: Shared<BlockScope>) -> Shared<Self> {
BlockScope::new(span, BlockScopeParent::Block(parent))
}

pub fn new(span: Span, parent: BlockScopeParent) -> Shared<Self> {
Rc::new(RefCell::new(BlockScope {
span,
parent,
defs: HashMap::new(),
Expand All @@ -147,26 +161,136 @@ impl FunctionScope {

#[allow(dead_code)]
pub fn module_scope(&self) -> Shared<ModuleScope> {
Rc::clone(&self.parent.borrow().parent)
Rc::clone(&self.contract_scope().borrow().parent)
}

/// Return the contract scope and its immediate block scope child
fn find_scope_boundary(&self) -> (Shared<ContractScope>, Shared<BlockScope>) {
let mut parent = self.parent.clone();
let mut last_block_scope = Rc::new(RefCell::new(self.clone()));
loop {
parent = match parent {
BlockScopeParent::Block(ref scope) => {
last_block_scope = scope.clone();
scope.borrow().parent.clone()
}
BlockScopeParent::Contract(ref scope) => return (scope.clone(), last_block_scope),
}
}
}

/// Return the contract scope that the block scope inherits from
pub fn contract_scope(&self) -> Shared<ContractScope> {
Rc::clone(&self.parent)
let (contract_scope, _) = self.find_scope_boundary();
contract_scope
}

/// Return the block scope that is associated with the function block
pub fn function_scope(&self) -> Shared<BlockScope> {
let (_, function_scope) = self.find_scope_boundary();
function_scope
}

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

pub fn def(&self, name: String) -> Option<FunctionDef> {
self.defs.get(&name).map(|def| (*def).clone())
/// Lookup definition in current or inherited block scope
pub fn def(&self, name: String) -> Option<BlockDef> {
let block_def = self.defs.get(&name).map(|def| (*def).clone());
if block_def.is_none() {
if let BlockScopeParent::Block(scope) = &self.parent {
scope.borrow().def(name)
} else {
None
}
} else {
block_def
}
}

pub fn add_array(&mut self, name: String, array: Array) {
self.defs.insert(name, FunctionDef::Array(array));
self.defs.insert(name, BlockDef::Array(array));
}

pub fn add_base(&mut self, name: String, base: Base) {
self.defs.insert(name, FunctionDef::Base(base));
self.defs.insert(name, BlockDef::Base(base));
}
}

#[cfg(test)]
mod tests {
use crate::namespace::scopes::{
BlockDef,
BlockScope,
ContractScope,
ModuleScope,
};
use crate::namespace::types::Base;
use fe_parser::span::Span;

#[test]
fn test_scope_resolution_on_first_level_block_scope() {
let module_scope = ModuleScope::new();
let contract_scope = ContractScope::new(module_scope);
let block_scope_1 =
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
assert_eq!(block_scope_1, block_scope_1.borrow().function_scope());
assert_eq!(contract_scope, block_scope_1.borrow().contract_scope());
}

#[test]
fn test_scope_resolution_on_second_level_block_scope() {
let module_scope = ModuleScope::new();
let contract_scope = ContractScope::new(module_scope);
let block_scope_1 =
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
let block_scope_2 = BlockScope::from_block_scope(Span::new(0, 0), block_scope_1.clone());
assert_eq!(block_scope_1, block_scope_2.borrow().function_scope());
assert_eq!(contract_scope, block_scope_2.borrow().contract_scope());
}

#[test]
fn test_1st_level_def_lookup_on_1st_level_block_scope() {
let module_scope = ModuleScope::new();
let contract_scope = ContractScope::new(module_scope);
let block_scope_1 =
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
block_scope_1
.borrow_mut()
.add_base("some_thing".to_string(), Base::Bool);
assert_eq!(
Some(BlockDef::Base(Base::Bool)),
block_scope_1.borrow().def("some_thing".to_string())
);
}

#[test]
fn test_1st_level_def_lookup_on_2nd_level_block_scope() {
let module_scope = ModuleScope::new();
let contract_scope = ContractScope::new(module_scope);
let block_scope_1 =
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
let block_scope_2 = BlockScope::from_block_scope(Span::new(0, 0), block_scope_1.clone());
block_scope_1
.borrow_mut()
.add_base("some_thing".to_string(), Base::Bool);
assert_eq!(
Some(BlockDef::Base(Base::Bool)),
block_scope_2.borrow().def("some_thing".to_string())
);
}

#[test]
fn test_2nd_level_def_lookup_on_1nd_level_block_scope_fails() {
let module_scope = ModuleScope::new();
let contract_scope = ContractScope::new(module_scope);
let block_scope_1 =
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
let block_scope_2 = BlockScope::from_block_scope(Span::new(0, 0), block_scope_1.clone());
block_scope_2
.borrow_mut()
.add_base("some_thing".to_string(), Base::Bool);
assert_eq!(None, block_scope_1.borrow().def("some_thing".to_string()));
}
}
16 changes: 8 additions & 8 deletions semantics/src/traversal/assignments.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::errors::SemanticError;
use crate::namespace::operations;
use crate::namespace::scopes::{
FunctionScope,
BlockScope,
Shared,
};
use crate::traversal::expressions;
Expand All @@ -14,7 +14,7 @@ use std::rc::Rc;
///
/// e.g. `foo[42] = "bar"`, `self.foo[42] = "bar"`, `foo = 42`
pub fn assign(
scope: Shared<FunctionScope>,
scope: Shared<BlockScope>,
context: Shared<Context>,
stmt: &Spanned<fe::FuncStmt>,
) -> Result<(), SemanticError> {
Expand Down Expand Up @@ -44,7 +44,7 @@ pub fn assign(
///
/// e.g. `foo[42] = "bar"`, `self.foo[42] = "bar"`
fn assign_subscript(
scope: Shared<FunctionScope>,
scope: Shared<BlockScope>,
context: Shared<Context>,
target: &Spanned<fe::Expr>,
value: &Spanned<fe::Expr>,
Expand Down Expand Up @@ -74,7 +74,7 @@ fn assign_subscript(
///
/// e.g. `foo = 42`
fn assign_name(
scope: Shared<FunctionScope>,
scope: Shared<BlockScope>,
context: Shared<Context>,
target: &Spanned<fe::Expr>,
value: &Spanned<fe::Expr>,
Expand All @@ -93,8 +93,8 @@ fn assign_name(
mod tests {
use crate::errors::SemanticError;
use crate::namespace::scopes::{
BlockScope,
ContractScope,
FunctionScope,
ModuleScope,
Shared,
};
Expand All @@ -115,7 +115,7 @@ mod tests {
// - self.foobar: Map<u256, u256>
// - foo: u256
// - bar: u256[100]
fn scope() -> Shared<FunctionScope> {
fn scope() -> Shared<BlockScope> {
let module_scope = ModuleScope::new();
let contract_scope = ContractScope::new(module_scope);
contract_scope.borrow_mut().add_map(
Expand All @@ -125,7 +125,7 @@ mod tests {
value: Box::new(Type::Base(Base::U256)),
},
);
let function_scope = FunctionScope::new(Span::new(0, 0), contract_scope);
let function_scope = BlockScope::from_contract_scope(Span::new(0, 0), contract_scope);
function_scope
.borrow_mut()
.add_base("foo".to_string(), Base::U256);
Expand All @@ -139,7 +139,7 @@ mod tests {
function_scope
}

fn analyze(scope: Shared<FunctionScope>, src: &str) -> Result<Context, SemanticError> {
fn analyze(scope: Shared<BlockScope>, src: &str) -> Result<Context, SemanticError> {
let context = Context::new_shared();
let tokens = parser::get_parse_tokens(src).expect("Couldn't parse expression");
let assignment = &parser::parsers::assign_stmt(&tokens[..])
Expand Down
18 changes: 9 additions & 9 deletions semantics/src/traversal/declarations.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::errors::SemanticError;
use crate::namespace::scopes::{
FunctionScope,
BlockScope,
Scope,
Shared,
};
Expand All @@ -16,13 +16,13 @@ use std::rc::Rc;

/// Gather context information for var declarations and check for type errors.
pub fn var_decl(
scope: Shared<FunctionScope>,
scope: Shared<BlockScope>,
context: Shared<Context>,
stmt: &Spanned<fe::FuncStmt>,
) -> Result<(), SemanticError> {
if let fe::FuncStmt::VarDecl { target, typ, value } = &stmt.node {
let name = expressions::expr_name_string(target)?;
let declared_type = types::type_desc_fixed_size(Scope::Function(Rc::clone(&scope)), typ)?;
let declared_type = types::type_desc_fixed_size(Scope::Block(Rc::clone(&scope)), typ)?;
if let Some(value) = value {
let value_attributes =
expressions::expr(Rc::clone(&scope), Rc::clone(&context), value)?;
Expand All @@ -48,9 +48,9 @@ pub fn var_decl(
mod tests {
use crate::errors::SemanticError;
use crate::namespace::scopes::{
BlockDef,
BlockScope,
ContractScope,
FunctionDef,
FunctionScope,
ModuleScope,
Shared,
};
Expand All @@ -61,13 +61,13 @@ mod tests {
use fe_parser::span::Span;
use std::rc::Rc;

fn scope() -> Shared<FunctionScope> {
fn scope() -> Shared<BlockScope> {
let module_scope = ModuleScope::new();
let contract_scope = ContractScope::new(module_scope);
FunctionScope::new(Span::new(0, 0), contract_scope)
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope)
}

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

Expand Down
Loading

0 comments on commit 05b6f3d

Please sign in to comment.