in rhai/src/optimizer.rs [386:855]
fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: bool) {
match stmt {
// var = var op expr => var op= expr
Stmt::Assignment(x, ..)
if !x.0.is_op_assignment()
&& x.1.lhs.is_variable_access(true)
&& matches!(&x.1.rhs, Expr::FnCall(x2, ..)
if Token::lookup_symbol_from_syntax(&x2.name).map_or(false, |t| t.has_op_assignment())
&& x2.args.len() == 2
&& x2.args[0].get_variable_name(true) == x.1.lhs.get_variable_name(true)
) =>
{
match x.1.rhs {
Expr::FnCall(ref mut x2, pos) => {
state.set_dirty();
x.0 = OpAssignment::new_op_assignment_from_base(&x2.name, pos);
x.1.rhs = x2.args[1].take();
}
ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr),
}
}
// expr op= expr
Stmt::Assignment(x, ..) => {
if !x.1.lhs.is_variable_access(false) {
optimize_expr(&mut x.1.lhs, state, false);
}
optimize_expr(&mut x.1.rhs, state, false);
}
// if expr {}
Stmt::If(x, ..) if x.body.is_empty() && x.branch.is_empty() => {
let condition = &mut x.expr;
state.set_dirty();
let pos = condition.start_position();
let mut expr = condition.take();
optimize_expr(&mut expr, state, false);
*stmt = if preserve_result {
// -> { expr, Noop }
(
[Stmt::Expr(expr.into()), Stmt::Noop(pos)],
pos,
Position::NONE,
)
.into()
} else {
// -> expr
Stmt::Expr(expr.into())
};
}
// if false { if_block } -> Noop
Stmt::If(x, ..)
if matches!(x.expr, Expr::BoolConstant(false, ..)) && x.branch.is_empty() =>
{
if let Expr::BoolConstant(false, pos) = x.expr {
state.set_dirty();
*stmt = Stmt::Noop(pos);
} else {
unreachable!("`Expr::BoolConstant`");
}
}
// if false { if_block } else { else_block } -> else_block
Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => {
state.set_dirty();
let body = x.branch.take_statements();
*stmt = match optimize_stmt_block(body, state, preserve_result, true, false) {
statements if statements.is_empty() => Stmt::Noop(x.branch.position()),
statements => (statements, x.branch.span()).into(),
}
}
// if true { if_block } else { else_block } -> if_block
Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(true, ..)) => {
state.set_dirty();
let body = x.body.take_statements();
*stmt = match optimize_stmt_block(body, state, preserve_result, true, false) {
statements if statements.is_empty() => Stmt::Noop(x.body.position()),
statements => (statements, x.body.span()).into(),
}
}
// if expr { if_block } else { else_block }
Stmt::If(x, ..) => {
let FlowControl { expr, body, branch } = &mut **x;
optimize_expr(expr, state, false);
let statements = body.take_statements();
**body = optimize_stmt_block(statements, state, preserve_result, true, false);
let statements = branch.take_statements();
**branch = optimize_stmt_block(statements, state, preserve_result, true, false);
}
// switch const { ... }
Stmt::Switch(x, pos) if x.0.is_constant() => {
let (
match_expr,
SwitchCasesCollection {
expressions,
cases,
ranges,
def_case,
},
) = &mut **x;
let value = match_expr.get_literal_value().unwrap();
let hasher = &mut get_hasher();
value.hash(hasher);
let hash = hasher.finish();
// First check hashes
if let Some(case_blocks_list) = cases.get(&hash) {
match &case_blocks_list[..] {
[] => (),
[index] => {
let mut b = mem::take(&mut expressions[*index]);
cases.clear();
if b.is_always_true() {
// Promote the matched case
let mut statements = Stmt::Expr(b.expr.take().into());
optimize_stmt(&mut statements, state, true);
*stmt = statements;
} else {
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(&mut b.condition, state, false);
let branch = match def_case {
Some(index) => {
let mut def_stmt =
Stmt::Expr(expressions[*index].expr.take().into());
optimize_stmt(&mut def_stmt, state, true);
def_stmt.into()
}
_ => StmtBlock::NONE,
};
let body = Stmt::Expr(b.expr.take().into()).into();
let expr = b.condition.take();
*stmt = Stmt::If(
FlowControl { expr, body, branch }.into(),
match_expr.start_position(),
);
}
state.set_dirty();
return;
}
_ => {
for &index in case_blocks_list {
let mut b = mem::take(&mut expressions[index]);
if b.is_always_true() {
// Promote the matched case
let mut statements = Stmt::Expr(b.expr.take().into());
optimize_stmt(&mut statements, state, true);
*stmt = statements;
state.set_dirty();
return;
}
}
}
}
}
// Then check ranges
if !ranges.is_empty() {
// Only one range or all ranges without conditions
if ranges.len() == 1
|| ranges
.iter()
.all(|r| expressions[r.index()].is_always_true())
{
if let Some(r) = ranges.iter().find(|r| r.contains(&value)) {
let range_block = &mut expressions[r.index()];
if range_block.is_always_true() {
// Promote the matched case
let block = &mut expressions[r.index()];
let mut statements = Stmt::Expr(block.expr.take().into());
optimize_stmt(&mut statements, state, true);
*stmt = statements;
} else {
let mut expr = range_block.condition.take();
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(&mut expr, state, false);
let branch = match def_case {
Some(index) => {
let mut def_stmt =
Stmt::Expr(expressions[*index].expr.take().into());
optimize_stmt(&mut def_stmt, state, true);
def_stmt.into()
}
_ => StmtBlock::NONE,
};
let body = Stmt::Expr(expressions[r.index()].expr.take().into()).into();
*stmt = Stmt::If(
FlowControl { expr, body, branch }.into(),
match_expr.start_position(),
);
}
state.set_dirty();
return;
}
} else {
// Multiple ranges - clear the table and just keep the right ranges
if !cases.is_empty() {
state.set_dirty();
cases.clear();
}
let old_ranges_len = ranges.len();
ranges.retain(|r| r.contains(&value));
if ranges.len() != old_ranges_len {
state.set_dirty();
}
ranges.iter().for_each(|r| {
let b = &mut expressions[r.index()];
optimize_expr(&mut b.condition, state, false);
optimize_expr(&mut b.expr, state, false);
});
return;
}
}
// Promote the default case
state.set_dirty();
match def_case {
Some(index) => {
let mut def_stmt = Stmt::Expr(expressions[*index].expr.take().into());
optimize_stmt(&mut def_stmt, state, true);
*stmt = def_stmt;
}
_ => *stmt = StmtBlock::empty(*pos).into(),
}
}
// switch
Stmt::Switch(x, ..) => {
let (
match_expr,
SwitchCasesCollection {
expressions,
cases,
ranges,
def_case,
..
},
) = &mut **x;
optimize_expr(match_expr, state, false);
// Optimize blocks
expressions.iter_mut().for_each(|b| {
optimize_expr(&mut b.condition, state, false);
optimize_expr(&mut b.expr, state, false);
if b.is_always_false() && !b.expr.is_unit() {
b.expr = Expr::Unit(b.expr.position());
state.set_dirty();
}
});
// Remove false cases
cases.retain(|_, list| {
// Remove all entries that have false conditions
list.retain(|index| {
if expressions[*index].is_always_false() {
state.set_dirty();
false
} else {
true
}
});
// Remove all entries after a `true` condition
if let Some(n) = list
.iter()
.position(|&index| expressions[index].is_always_true())
{
if n + 1 < list.len() {
state.set_dirty();
list.truncate(n + 1);
}
}
// Remove if no entry left
if list.is_empty() {
state.set_dirty();
false
} else {
true
}
});
// Remove false ranges
ranges.retain(|r| {
if expressions[r.index()].is_always_false() {
state.set_dirty();
false
} else {
true
}
});
if let Some(index) = def_case {
optimize_expr(&mut expressions[*index].expr, state, false);
}
// Remove unused block statements
(0..expressions.len()).into_iter().for_each(|index| {
if *def_case == Some(index)
|| cases.values().flat_map(|c| c.iter()).any(|&n| n == index)
|| ranges.iter().any(|r| r.index() == index)
{
return;
}
let b = &mut expressions[index];
if !b.expr.is_unit() {
b.expr = Expr::Unit(b.expr.position());
state.set_dirty();
}
});
}
// while false { block } -> Noop
Stmt::While(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => match x.expr {
Expr::BoolConstant(false, pos) => {
state.set_dirty();
*stmt = Stmt::Noop(pos);
}
_ => unreachable!("`Expr::BoolConstant"),
},
// while expr { block }
Stmt::While(x, ..) => {
let FlowControl { expr, body, .. } = &mut **x;
optimize_expr(expr, state, false);
if let Expr::BoolConstant(true, pos) = expr {
*expr = Expr::Unit(*pos);
}
**body = optimize_stmt_block(body.take_statements(), state, false, true, false);
}
// do { block } while|until expr
Stmt::Do(x, ..) => {
optimize_expr(&mut x.expr, state, false);
*x.body = optimize_stmt_block(x.body.take_statements(), state, false, true, false);
}
// for id in expr { block }
Stmt::For(x, ..) => {
optimize_expr(&mut x.2.expr, state, false);
*x.2.body = optimize_stmt_block(x.2.body.take_statements(), state, false, true, false);
}
// let id = expr;
Stmt::Var(x, options, ..) if !options.contains(ASTFlags::CONSTANT) => {
optimize_expr(&mut x.1, state, false);
}
// import expr as var;
#[cfg(not(feature = "no_module"))]
Stmt::Import(x, ..) => optimize_expr(&mut x.0, state, false),
// { block }
Stmt::Block(block) => {
let span = block.span();
let statements = block.take_statements().into_vec().into();
let mut block = optimize_stmt_block(statements, state, preserve_result, true, false);
match block.as_mut_slice() {
[] => {
state.set_dirty();
*stmt = Stmt::Noop(span.start());
}
// Only one statement which is not block-dependent - promote
[s] if !s.is_block_dependent() => {
state.set_dirty();
*stmt = s.take();
}
_ => *stmt = (block, span).into(),
}
}
// try { pure try_block } catch ( var ) { catch_block } -> try_block
Stmt::TryCatch(x, ..) if x.body.iter().all(Stmt::is_pure) => {
// If try block is pure, there will never be any exceptions
state.set_dirty();
*stmt = (
optimize_stmt_block(x.body.take_statements(), state, false, true, false),
x.body.span(),
)
.into();
}
// try { try_block } catch ( var ) { catch_block }
Stmt::TryCatch(x, ..) => {
*x.body = optimize_stmt_block(x.body.take_statements(), state, false, true, false);
*x.branch = optimize_stmt_block(x.branch.take_statements(), state, false, true, false);
}
// expr(stmt)
Stmt::Expr(expr) if matches!(**expr, Expr::Stmt(..)) => {
state.set_dirty();
match expr.as_mut() {
Expr::Stmt(block) if !block.is_empty() => {
let mut stmt_block = *mem::take(block);
*stmt_block =
optimize_stmt_block(stmt_block.take_statements(), state, true, true, false);
*stmt = stmt_block.into();
}
Expr::Stmt(..) => *stmt = Stmt::Noop(expr.position()),
_ => unreachable!("`Expr::Stmt`"),
}
}
// expr(func())
Stmt::Expr(expr) if matches!(**expr, Expr::FnCall(..)) => {
state.set_dirty();
match expr.take() {
Expr::FnCall(x, pos) => *stmt = Stmt::FnCall(x, pos),
_ => unreachable!(),
}
}
Stmt::Expr(expr) => optimize_expr(expr, state, false),
// func(...)
Stmt::FnCall(..) => {
if let Stmt::FnCall(x, pos) = stmt.take() {
let mut expr = Expr::FnCall(x, pos);
optimize_expr(&mut expr, state, false);
*stmt = match expr {
Expr::FnCall(x, pos) => Stmt::FnCall(x, pos),
_ => Stmt::Expr(expr.into()),
}
} else {
unreachable!();
}
}
// break expr;
Stmt::BreakLoop(Some(ref mut expr), ..) => optimize_expr(expr, state, false),
// return expr;
Stmt::Return(Some(ref mut expr), ..) => optimize_expr(expr, state, false),
// Share nothing
#[cfg(not(feature = "no_closure"))]
Stmt::Share(x) if x.is_empty() => {
state.set_dirty();
*stmt = Stmt::Noop(Position::NONE);
}
// Share constants
#[cfg(not(feature = "no_closure"))]
Stmt::Share(x) => {
let orig_len = x.len();
if state.propagate_constants {
x.retain(|(v, _)| state.find_literal_constant(v).is_none());
if x.len() != orig_len {
state.set_dirty();
}
}
}
// All other statements - skip
_ => (),
}
}