Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lowering pass and augmented assignments #338

Merged
merged 1 commit into from
Mar 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions analyzer/src/traversal/assignments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ use crate::namespace::scopes::{
BlockScope,
Shared,
};
use crate::namespace::types::{
Base,
Type,
};
use crate::traversal::expressions;
use crate::Context;
use crate::Location;
Expand Down Expand Up @@ -50,6 +54,36 @@ pub fn assign(
unreachable!()
}

/// Gather context information for assignments and check for type errors.
pub fn aug_assign(
g-r-a-n-t marked this conversation as resolved.
Show resolved Hide resolved
scope: Shared<BlockScope>,
context: Shared<Context>,
stmt: &Node<fe::FuncStmt>,
) -> Result<(), SemanticError> {
if let fe::FuncStmt::AugAssign {
target,
op: _,
value,
} = &stmt.kind
{
let target_attributes = expressions::expr(Rc::clone(&scope), Rc::clone(&context), target)?;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

relevant: #345

let value_attributes = expressions::expr(scope, context, value)?;

return match target_attributes.typ {
Type::Base(Base::Numeric(_)) => {
if target_attributes.typ == value_attributes.typ {
Ok(())
} else {
Err(SemanticError::type_error())
}
}
_ => Err(SemanticError::type_error()),
};
}

unreachable!()
}

#[cfg(test)]
mod tests {
use crate::errors::{
Expand Down
2 changes: 1 addition & 1 deletion analyzer/src/traversal/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ fn func_stmt(
fe::FuncStmt::VarDecl { .. } => declarations::var_decl(scope, context, stmt),
fe::FuncStmt::Assign { .. } => assignments::assign(scope, context, stmt),
fe::FuncStmt::Emit { .. } => emit(scope, context, stmt),
fe::FuncStmt::AugAssign { .. } => unimplemented!(),
fe::FuncStmt::AugAssign { .. } => assignments::aug_assign(scope, context, stmt),
fe::FuncStmt::For { .. } => for_loop(scope, context, stmt),
fe::FuncStmt::While { .. } => while_loop(scope, context, stmt),
fe::FuncStmt::If { .. } => if_statement(scope, context, stmt),
Expand Down
7 changes: 4 additions & 3 deletions compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub fn compile(
let json_abis = abi::build(&context, &fe_module)?;

// lower the AST
let lowered_fe_module = lowering::lower(&context, &fe_module);
let lowered_fe_module = lowering::lower(&context, fe_module.clone());

// analyze the lowered AST
let context = fe_analyzer::analyze(&lowered_fe_module)
Expand Down Expand Up @@ -81,8 +81,9 @@ pub fn compile(
.collect::<NamedContracts>();

Ok(CompiledModule {
fe_tokens: format!("{:#?}", fe_tokens),
fe_ast: format!("{:#?}", fe_module),
src_tokens: format!("{:#?}", fe_tokens),
src_ast: format!("{:#?}", fe_module),
lowered_ast: format!("{:#?}", lowered_fe_module),
contracts,
})
}
1 change: 0 additions & 1 deletion compiler/src/lowering/mappers/assignments.rs

This file was deleted.

29 changes: 29 additions & 0 deletions compiler/src/lowering/mappers/contracts.rs
Original file line number Diff line number Diff line change
@@ -1 +1,30 @@
use fe_analyzer::Context;

use crate::lowering::mappers::functions;
use fe_parser::ast as fe;
use fe_parser::ast::ContractStmt;
use fe_parser::node::Node;

/// Lowers a contract definition.
pub fn contract_def(context: &Context, stmt: Node<fe::ModuleStmt>) -> Node<fe::ModuleStmt> {
g-r-a-n-t marked this conversation as resolved.
Show resolved Hide resolved
if let fe::ModuleStmt::ContractDef { name, body } = stmt.kind {
let lowered_body = body
.into_iter()
.map(|stmt| match stmt.kind {
ContractStmt::ContractField { .. } => stmt,
ContractStmt::EventDef { .. } => stmt,
ContractStmt::FuncDef { .. } => functions::func_def(context, stmt),
})
.collect();

return Node::new(
fe::ModuleStmt::ContractDef {
name,
body: lowered_body,
},
stmt.span,
);
}

unreachable!()
}
1 change: 0 additions & 1 deletion compiler/src/lowering/mappers/declarations.rs

This file was deleted.

122 changes: 122 additions & 0 deletions compiler/src/lowering/mappers/expressions.rs
Original file line number Diff line number Diff line change
@@ -1 +1,123 @@
use fe_analyzer::Context;
use fe_parser::ast as fe;
use fe_parser::node::Node;

/// Lowers an expression and all sub expressions.
pub fn expr(context: &Context, exp: Node<fe::Expr>) -> Node<fe::Expr> {
let lowered_kind = match exp.kind {
fe::Expr::Name(_) => exp.kind,
fe::Expr::Num(_) => exp.kind,
fe::Expr::Bool(_) => exp.kind,
fe::Expr::Subscript { value, slices } => fe::Expr::Subscript {
value: boxed_expr(context, value),
slices: slices_index_expr(context, slices),
},
fe::Expr::Attribute { value, attr } => fe::Expr::Attribute {
value: boxed_expr(context, value),
attr,
},
fe::Expr::Ternary {
if_expr,
test,
else_expr,
} => fe::Expr::Ternary {
if_expr: boxed_expr(context, if_expr),
test: boxed_expr(context, test),
else_expr: boxed_expr(context, else_expr),
},
fe::Expr::BoolOperation { left, op, right } => fe::Expr::BoolOperation {
left: boxed_expr(context, left),
op,
right: boxed_expr(context, right),
},
fe::Expr::BinOperation { left, op, right } => fe::Expr::BinOperation {
left: boxed_expr(context, left),
op,
right: boxed_expr(context, right),
},
fe::Expr::UnaryOperation { op, operand } => fe::Expr::UnaryOperation {
op,
operand: boxed_expr(context, operand),
},
fe::Expr::CompOperation { left, op, right } => fe::Expr::CompOperation {
left: boxed_expr(context, left),
op,
right: boxed_expr(context, right),
},
fe::Expr::Call { func, args } => fe::Expr::Call {
func: boxed_expr(context, func),
args: call_args(context, args),
},
fe::Expr::List { .. } => unimplemented!(),
fe::Expr::ListComp { .. } => unimplemented!(),
// We only accept empty tuples for now. We may want to completely eliminate tuple
// expressions before the Yul codegen pass, tho.
fe::Expr::Tuple { .. } => exp.kind,
g-r-a-n-t marked this conversation as resolved.
Show resolved Hide resolved
fe::Expr::Str(_) => exp.kind,
fe::Expr::Ellipsis => unimplemented!(),
};

Node::new(lowered_kind, exp.span)
}

fn slices_index_expr(
context: &Context,
slices: Node<Vec<Node<fe::Slice>>>,
) -> Node<Vec<Node<fe::Slice>>> {
let first_slice = &slices.kind[0];

if let fe::Slice::Index(exp) = &first_slice.kind {
return Node::new(
vec![Node::new(
fe::Slice::Index(Box::new(expr(context, *exp.to_owned()))),
first_slice.span,
)],
slices.span,
);
}

unreachable!()
}

/// Lowers and optional expression.
pub fn optional_expr(context: &Context, exp: Option<Node<fe::Expr>>) -> Option<Node<fe::Expr>> {
g-r-a-n-t marked this conversation as resolved.
Show resolved Hide resolved
exp.map(|exp| expr(context, exp))
}

/// Lowers a boxed expression.
#[allow(clippy::boxed_local)]
pub fn boxed_expr(context: &Context, exp: Box<Node<fe::Expr>>) -> Box<Node<fe::Expr>> {
g-r-a-n-t marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another pattern that might be worth optimizing in the future, or might not matter; we take a boxed expr, probably don't modify it, and rebox. An extra allocation for every expr.

Box::new(expr(context, *exp))
}

/// Lowers a list of expression.
pub fn multiple_exprs(context: &Context, exp: Vec<Node<fe::Expr>>) -> Vec<Node<fe::Expr>> {
g-r-a-n-t marked this conversation as resolved.
Show resolved Hide resolved
exp.into_iter().map(|exp| expr(context, exp)).collect()
}

fn call_args(
context: &Context,
args: Node<Vec<Node<fe::CallArg>>>,
) -> Node<Vec<Node<fe::CallArg>>> {
let lowered_args = args
.kind
.into_iter()
.map(|arg| match arg.kind {
fe::CallArg::Arg(inner_arg) => {
Node::new(fe::CallArg::Arg(expr(context, inner_arg)), arg.span)
}
fe::CallArg::Kwarg(inner_arg) => {
Node::new(fe::CallArg::Kwarg(kwarg(context, inner_arg)), arg.span)
}
})
.collect();

Node::new(lowered_args, args.span)
}

fn kwarg(context: &Context, kwarg: fe::Kwarg) -> fe::Kwarg {
fe::Kwarg {
name: kwarg.name,
value: boxed_expr(context, kwarg.value),
}
}
129 changes: 129 additions & 0 deletions compiler/src/lowering/mappers/functions.rs
Original file line number Diff line number Diff line change
@@ -1 +1,130 @@
use crate::lowering::mappers::expressions;
use fe_analyzer::Context;
use fe_parser::ast as fe;
use fe_parser::node::Node;

/// Lowers a function definition.
pub fn func_def(context: &Context, def: Node<fe::ContractStmt>) -> Node<fe::ContractStmt> {
if let fe::ContractStmt::FuncDef {
qual,
name,
args,
return_type,
body,
} = def.kind
{
let lowered_body = multiple_stmts(context, body);

let lowered_kind = fe::ContractStmt::FuncDef {
qual,
name,
args,
return_type,
body: lowered_body,
};

return Node::new(lowered_kind, def.span);
}

unreachable!()
}

fn func_stmt(context: &Context, stmt: Node<fe::FuncStmt>) -> Vec<Node<fe::FuncStmt>> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: returning a vec here because we'll have lowerings like:

(foo, bar): (u256, u256) = my_tuple

to

foo: u256 = my_tuple.item0
bar: u256 = my_tuple.item1

which requires that we be able to map a single statement to multiple statements.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good candidate for SmallVec<[Node<..>, 1]> in some possible future performance optimization.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I'll track this in an issue.

let lowered_kinds = match stmt.kind {
fe::FuncStmt::Return { value } => vec![fe::FuncStmt::Return {
value: expressions::optional_expr(context, value),
}],
fe::FuncStmt::VarDecl { target, typ, value } => vec![fe::FuncStmt::VarDecl {
target: expressions::expr(context, target),
typ,
value: expressions::optional_expr(context, value),
}],
fe::FuncStmt::Assign { targets, value } => vec![fe::FuncStmt::Assign {
targets: expressions::multiple_exprs(context, targets),
value: expressions::expr(context, value),
}],
fe::FuncStmt::Emit { value } => vec![fe::FuncStmt::Emit {
value: expressions::expr(context, value),
}],
fe::FuncStmt::AugAssign { target, op, value } => aug_assign(context, target, op, value),
fe::FuncStmt::For {
target,
iter,
body,
or_else,
} => vec![fe::FuncStmt::For {
target: expressions::expr(context, target),
iter: expressions::expr(context, iter),
body: multiple_stmts(context, body),
or_else: multiple_stmts(context, or_else),
}],
fe::FuncStmt::While {
test,
body,
or_else,
} => vec![fe::FuncStmt::While {
test: expressions::expr(context, test),
body: multiple_stmts(context, body),
or_else: multiple_stmts(context, or_else),
}],
fe::FuncStmt::If {
test,
body,
or_else,
} => vec![fe::FuncStmt::If {
test: expressions::expr(context, test),
body: multiple_stmts(context, body),
or_else: multiple_stmts(context, or_else),
}],
fe::FuncStmt::Assert { test, msg } => vec![fe::FuncStmt::Assert {
test: expressions::expr(context, test),
msg: expressions::optional_expr(context, msg),
}],
fe::FuncStmt::Expr { value } => vec![fe::FuncStmt::Expr {
value: expressions::expr(context, value),
}],
fe::FuncStmt::Pass => vec![stmt.kind],
fe::FuncStmt::Break => vec![stmt.kind],
fe::FuncStmt::Continue => vec![stmt.kind],
fe::FuncStmt::Revert => vec![stmt.kind],
};
let span = stmt.span;

lowered_kinds
.into_iter()
.map(|kind| Node::new(kind, span))
.collect()
}

fn multiple_stmts(context: &Context, stmts: Vec<Node<fe::FuncStmt>>) -> Vec<Node<fe::FuncStmt>> {
stmts
.into_iter()
.map(|stmt| func_stmt(context, stmt))
.collect::<Vec<Vec<Node<fe::FuncStmt>>>>()
.concat()
}

fn aug_assign(
context: &Context,
target: Node<fe::Expr>,
op: Node<fe::BinOperator>,
value: Node<fe::Expr>,
) -> Vec<fe::FuncStmt> {
let lowered_target = expressions::expr(context, target);
let original_value_span = value.span;
let lowered_value = expressions::expr(context, value);

let new_value_kind = fe::Expr::BinOperation {
left: Box::new(lowered_target.clone().new_id()),
op,
right: Box::new(lowered_value),
};

let new_value = Node::new(new_value_kind, original_value_span);

// the new statement is: `target = target <op> value`.
vec![fe::FuncStmt::Assign {
targets: vec![lowered_target],
value: new_value,
}]
}
2 changes: 0 additions & 2 deletions compiler/src/lowering/mappers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
mod assignments;
mod contracts;
mod declarations;
mod expressions;
mod functions;
pub mod module;
20 changes: 20 additions & 0 deletions compiler/src/lowering/mappers/module.rs
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
use fe_analyzer::Context;

use crate::lowering::mappers::contracts;
use fe_parser::ast as fe;

/// Lowers a module.
pub fn module(context: &Context, module: fe::Module) -> fe::Module {
let lowered_body = module
.body
.into_iter()
.map(|stmt| match &stmt.kind {
fe::ModuleStmt::TypeDef { .. } => stmt,
fe::ModuleStmt::StructDef { .. } => stmt,
fe::ModuleStmt::FromImport { .. } => stmt,
fe::ModuleStmt::SimpleImport { .. } => stmt,
fe::ModuleStmt::ContractDef { .. } => contracts::contract_def(context, stmt),
})
.collect();

fe::Module { body: lowered_body }
}
Loading