fn optimize_stmt()

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
        _ => (),
    }
}