Skip to main content

rhai/
optimizer.rs

1//! Module implementing the [`AST`] optimizer.
2#![cfg(not(feature = "no_optimize"))]
3
4use crate::ast::{
5    ASTFlags, Expr, FlowControl, OpAssignment, Stmt, StmtBlock, StmtBlockContainer,
6    SwitchCasesCollection,
7};
8use crate::engine::{
9    KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT,
10    KEYWORD_TYPE_OF, OP_NOT,
11};
12use crate::eval::{Caches, GlobalRuntimeState};
13use crate::func::builtin::get_builtin_binary_op_fn;
14use crate::func::hashing::get_hasher;
15use crate::tokenizer::Token;
16use crate::{
17    calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, Position,
18    Scope, StaticVec, AST,
19};
20#[cfg(feature = "no_std")]
21use std::prelude::v1::*;
22use std::{
23    any::TypeId,
24    borrow::Cow,
25    convert::TryFrom,
26    hash::{Hash, Hasher},
27    mem,
28};
29
30/// Level of optimization performed.
31///
32/// Not available under `no_optimize`.
33#[derive(Debug, Eq, PartialEq, Clone, Copy, Default, Hash)]
34#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
35#[non_exhaustive]
36pub enum OptimizationLevel {
37    /// No optimization performed.
38    None,
39    /// Only perform simple optimizations without evaluating functions.
40    #[default]
41    Simple,
42    /// Full optimizations performed, including evaluating functions.
43    /// Take care that this may cause side effects as it essentially assumes that all functions are pure.
44    Full,
45}
46
47/// Mutable state throughout an optimization pass.
48#[derive(Debug, Clone)]
49struct OptimizerState<'a> {
50    /// Has the [`AST`] been changed during this pass?
51    is_dirty: bool,
52    /// Stack of variables/constants for constants propagation and strict variables checking.
53    variables: Vec<(ImmutableString, Option<Cow<'a, Dynamic>>)>,
54    /// Activate constants propagation?
55    propagate_constants: bool,
56    /// [`Engine`] instance for eager function evaluation.
57    engine: &'a Engine,
58    /// Optional [`Scope`].
59    scope: Option<&'a Scope<'a>>,
60    /// The global runtime state.
61    global: GlobalRuntimeState,
62    /// Function resolution caches.
63    caches: Caches,
64    /// Optimization level.
65    optimization_level: OptimizationLevel,
66}
67
68impl<'a> OptimizerState<'a> {
69    /// Create a new [`OptimizerState`].
70    #[inline(always)]
71    pub fn new(
72        engine: &'a Engine,
73        lib: &'a [crate::SharedModule],
74        scope: Option<&'a Scope<'a>>,
75        optimization_level: OptimizationLevel,
76    ) -> Self {
77        let mut _global = engine.new_global_runtime_state();
78        let _lib = lib;
79
80        #[cfg(not(feature = "no_function"))]
81        {
82            _global.lib = _lib.into();
83        }
84
85        Self {
86            is_dirty: false,
87            variables: Vec::new(),
88            propagate_constants: true,
89            engine,
90            scope,
91            global: _global,
92            caches: Caches::new(),
93            optimization_level,
94        }
95    }
96    /// Set the [`AST`] state to be dirty (i.e. changed).
97    #[inline(always)]
98    pub fn set_dirty(&mut self) {
99        self.is_dirty = true;
100    }
101    /// Set the [`AST`] state to be not dirty (i.e. unchanged).
102    #[inline(always)]
103    pub fn clear_dirty(&mut self) {
104        self.is_dirty = false;
105    }
106    /// Is the [`AST`] dirty (i.e. changed)?
107    #[inline(always)]
108    pub const fn is_dirty(&self) -> bool {
109        self.is_dirty
110    }
111    /// Rewind the variables stack back to a specified size.
112    #[inline(always)]
113    pub fn rewind_var(&mut self, len: usize) {
114        self.variables.truncate(len);
115    }
116    /// Add a new variable to the stack.
117    ///
118    /// `Some(value)` if literal constant (which can be used for constants propagation), `None` otherwise.
119    #[inline(always)]
120    pub fn push_var<'x: 'a>(&mut self, name: ImmutableString, value: Option<Cow<'x, Dynamic>>) {
121        self.variables.push((name, value));
122    }
123    /// Look up a literal constant from the variables stack.
124    #[inline]
125    pub fn find_literal_constant(&self, name: &str) -> Option<&Dynamic> {
126        self.variables
127            .iter()
128            .rev()
129            .find(|(n, _)| n == name)
130            .and_then(|(_, value)| value.as_deref())
131    }
132    /// Call a registered function
133    #[inline]
134    pub fn call_fn_with_const_args(
135        &mut self,
136        fn_name: &str,
137        op_token: Option<&Token>,
138        arg_values: &mut [Dynamic],
139    ) -> Option<Dynamic> {
140        self.engine
141            .exec_native_fn_call(
142                &mut self.global,
143                &mut self.caches,
144                fn_name,
145                op_token,
146                calc_fn_hash(None, fn_name, arg_values.len()),
147                &mut arg_values.iter_mut().collect::<FnArgsVec<_>>(),
148                false,
149                true,
150                Position::NONE,
151            )
152            .ok()
153            .map(|(v, ..)| v)
154    }
155}
156
157/// Optimize a block of [statements][Stmt].
158fn optimize_stmt_block(
159    mut statements: StmtBlockContainer,
160    state: &mut OptimizerState,
161    preserve_result: bool,
162    is_internal: bool,
163    reduce_return: bool,
164) -> StmtBlockContainer {
165    if statements.is_empty() {
166        return statements;
167    }
168
169    let mut is_dirty = state.is_dirty();
170
171    let is_pure = if is_internal {
172        Stmt::is_internally_pure
173    } else {
174        Stmt::is_pure
175    };
176
177    // Flatten blocks
178    while let Some(n) = statements.iter().position(
179        |s| matches!(s, Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent)),
180    ) {
181        let (first, second) = statements.split_at_mut(n);
182        let mut stmt = second[0].take();
183        let stmts = match stmt {
184            Stmt::Block(ref mut block, ..) => block.statements_mut(),
185            _ => unreachable!("Stmt::Block expected but gets {:?}", stmt),
186        };
187        statements = first
188            .iter_mut()
189            .map(mem::take)
190            .chain(stmts.iter_mut().map(mem::take))
191            .chain(second.iter_mut().skip(1).map(mem::take))
192            .collect();
193
194        is_dirty = true;
195    }
196
197    // Optimize
198    loop {
199        state.clear_dirty();
200
201        let orig_constants_len = state.variables.len(); // Original number of constants in the state, for restore later
202        let orig_propagate_constants = state.propagate_constants;
203
204        // Remove everything following control flow breaking statements
205        let mut dead_code = false;
206
207        statements.retain(|stmt| {
208            if dead_code {
209                state.set_dirty();
210                false
211            } else if stmt.is_control_flow_break() {
212                dead_code = true;
213                true
214            } else {
215                true
216            }
217        });
218
219        // Optimize each statement in the block
220        statements.iter_mut().for_each(|stmt| {
221            match stmt {
222                Stmt::Var(x, options, ..) => {
223                    optimize_expr(&mut x.1, state, false);
224
225                    let value = if options.intersects(ASTFlags::CONSTANT) && x.1.is_constant() {
226                        // constant literal
227                        Some(Cow::Owned(x.1.get_literal_value(None).unwrap()))
228                    } else {
229                        // variable
230                        None
231                    };
232                    state.push_var(x.0.name.clone(), value);
233                }
234                // Optimize the statement
235                _ => optimize_stmt(stmt, state, preserve_result),
236            }
237        });
238
239        // Remove all pure statements except the last one
240        let mut index = 0;
241        let mut first_non_constant = statements
242            .iter()
243            .rev()
244            .position(|stmt| match stmt {
245                stmt if !is_pure(stmt) => true,
246
247                Stmt::Var(x, ..) if x.1.is_constant() => true,
248                Stmt::Expr(e) if !e.is_constant() => true,
249
250                #[cfg(not(feature = "no_module"))]
251                Stmt::Import(x, ..) if !x.0.is_constant() => true,
252
253                _ => false,
254            })
255            .map_or(0, |n| statements.len() - n - 1);
256
257        while index < statements.len() {
258            if preserve_result && index >= statements.len() - 1 {
259                break;
260            }
261            match statements[index] {
262                ref stmt if is_pure(stmt) && index >= first_non_constant => {
263                    state.set_dirty();
264                    statements.remove(index);
265                }
266                ref stmt if stmt.is_pure() => {
267                    state.set_dirty();
268                    if index < first_non_constant {
269                        first_non_constant -= 1;
270                    }
271                    statements.remove(index);
272                }
273                _ => index += 1,
274            }
275        }
276
277        // Remove all pure statements that do not return values at the end of a block.
278        // We cannot remove anything for non-pure statements due to potential side-effects.
279        if preserve_result {
280            loop {
281                match statements[..] {
282                    // { return; } -> {}
283                    [Stmt::Return(None, options, ..)]
284                        if reduce_return && !options.intersects(ASTFlags::BREAK) =>
285                    {
286                        state.set_dirty();
287                        statements.clear();
288                    }
289                    [ref stmt] if !stmt.returns_value() && is_pure(stmt) => {
290                        state.set_dirty();
291                        statements.clear();
292                    }
293                    // { ...; return; } -> { ... }
294                    [.., ref last_stmt, Stmt::Return(None, options, ..)]
295                        if reduce_return
296                            && !options.intersects(ASTFlags::BREAK)
297                            && !last_stmt.returns_value() =>
298                    {
299                        state.set_dirty();
300                        statements.pop().unwrap();
301                    }
302                    // { ...; return val; } -> { ...; val }
303                    [.., Stmt::Return(ref mut expr, options, pos)]
304                        if reduce_return && !options.intersects(ASTFlags::BREAK) =>
305                    {
306                        state.set_dirty();
307                        *statements.last_mut().unwrap() = expr
308                            .as_mut()
309                            .map_or_else(|| Stmt::Noop(pos), |e| Stmt::Expr(mem::take(e)));
310                    }
311                    // { ...; stmt; noop } -> done
312                    [.., ref second_last_stmt, Stmt::Noop(..)]
313                        if second_last_stmt.returns_value() =>
314                    {
315                        break
316                    }
317                    // { ...; stmt_that_returns; pure_non_value_stmt } -> { ...; stmt_that_returns; noop }
318                    // { ...; stmt; pure_non_value_stmt } -> { ...; stmt }
319                    [.., ref second_last_stmt, ref last_stmt]
320                        if !last_stmt.returns_value() && is_pure(last_stmt) =>
321                    {
322                        state.set_dirty();
323                        if second_last_stmt.returns_value() {
324                            *statements.last_mut().unwrap() = Stmt::Noop(last_stmt.position());
325                        } else {
326                            statements.pop().unwrap();
327                        }
328                    }
329                    _ => break,
330                }
331            }
332        } else {
333            loop {
334                match statements[..] {
335                    [ref stmt] if is_pure(stmt) => {
336                        state.set_dirty();
337                        statements.clear();
338                    }
339                    // { ...; return; } -> { ... }
340                    [.., Stmt::Return(None, options, ..)]
341                        if reduce_return && !options.intersects(ASTFlags::BREAK) =>
342                    {
343                        state.set_dirty();
344                        statements.pop().unwrap();
345                    }
346                    // { ...; return pure_val; } -> { ... }
347                    [.., Stmt::Return(Some(ref expr), options, ..)]
348                        if reduce_return
349                            && !options.intersects(ASTFlags::BREAK)
350                            && expr.is_pure() =>
351                    {
352                        state.set_dirty();
353                        statements.pop().unwrap();
354                    }
355                    [.., ref last_stmt] if is_pure(last_stmt) => {
356                        state.set_dirty();
357                        statements.pop().unwrap();
358                    }
359                    _ => break,
360                }
361            }
362        }
363
364        // Pop the stack and remove all the local constants
365        state.rewind_var(orig_constants_len);
366        state.propagate_constants = orig_propagate_constants;
367
368        if !state.is_dirty() {
369            break;
370        }
371
372        is_dirty = true;
373    }
374
375    if is_dirty {
376        state.set_dirty();
377    }
378
379    statements.shrink_to_fit();
380    statements
381}
382
383impl StmtBlock {
384    #[inline(always)]
385    #[must_use]
386    fn take_statements(&mut self) -> StmtBlockContainer {
387        mem::take(self.statements_mut())
388    }
389}
390
391/// Is this [`Expr`] a constant that is hashable?
392#[inline(always)]
393fn is_hashable_constant(expr: &Expr) -> bool {
394    match expr {
395        _ if !expr.is_constant() => false,
396        Expr::DynamicConstant(v, ..) => v.is_hashable(),
397        _ => false,
398    }
399}
400
401/// Optimize a [statement][Stmt].
402fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: bool) {
403    #[inline(always)]
404    #[must_use]
405    fn is_variable_access(expr: &Expr, _non_qualified: bool) -> bool {
406        match expr {
407            #[cfg(not(feature = "no_module"))]
408            Expr::Variable(x, ..) if _non_qualified && !x.2.is_empty() => false,
409            Expr::Variable(..) => true,
410            _ => false,
411        }
412    }
413
414    match stmt {
415        // var = var op expr => var op= expr
416        Stmt::Assignment(x, ..)
417            if !x.0.is_op_assignment()
418                && is_variable_access(&x.1.lhs, true)
419                && matches!(&x.1.rhs, Expr::FnCall(x2, ..)
420                        if Token::lookup_symbol_from_syntax(&x2.name).map_or(false, |t| t.has_op_assignment())
421                        && x2.args.len() == 2
422                        && x2.args[0].get_variable_name(true) == x.1.lhs.get_variable_name(true)
423                ) =>
424        {
425            match x.1.rhs {
426                Expr::FnCall(ref mut x2, pos) => {
427                    state.set_dirty();
428                    x.0 = OpAssignment::new_op_assignment_from_base(&x2.name, pos);
429                    x.1.rhs = x2.args[1].take();
430                }
431                ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr),
432            }
433        }
434
435        // expr op= expr
436        Stmt::Assignment(x, ..) => {
437            if !is_variable_access(&x.1.lhs, false) {
438                optimize_expr(&mut x.1.lhs, state, false);
439            }
440            optimize_expr(&mut x.1.rhs, state, false);
441        }
442
443        // if expr {}
444        Stmt::If(x, ..) if x.body.is_empty() && x.branch.is_empty() => {
445            let condition = &mut x.expr;
446            state.set_dirty();
447
448            let pos = condition.start_position();
449            let mut expr = condition.take();
450            optimize_expr(&mut expr, state, false);
451
452            *stmt = if preserve_result {
453                // -> { expr, Noop }
454                Stmt::Block(
455                    StmtBlock::new(
456                        [Stmt::Expr(expr.into()), Stmt::Noop(pos)],
457                        pos,
458                        Position::NONE,
459                    )
460                    .into(),
461                )
462            } else {
463                // -> expr
464                Stmt::Expr(expr.into())
465            };
466        }
467        // if false { if_block } -> Noop
468        Stmt::If(x, ..)
469            if matches!(x.expr, Expr::BoolConstant(false, ..)) && x.branch.is_empty() =>
470        {
471            match x.expr {
472                Expr::BoolConstant(false, pos) => {
473                    state.set_dirty();
474                    *stmt = Stmt::Noop(pos);
475                }
476                _ => unreachable!("`Expr::BoolConstant`"),
477            }
478        }
479        // if false { if_block } else { else_block } -> else_block
480        Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => {
481            state.set_dirty();
482            let body = x.branch.take_statements();
483            *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) {
484                statements if statements.is_empty() => Stmt::Noop(x.branch.position()),
485                statements => {
486                    Stmt::Block(StmtBlock::new_with_span(statements, x.branch.span()).into())
487                }
488            }
489        }
490        // if true { if_block } else { else_block } -> if_block
491        Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(true, ..)) => {
492            state.set_dirty();
493            let body = x.body.take_statements();
494            *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) {
495                statements if statements.is_empty() => Stmt::Noop(x.body.position()),
496                statements => {
497                    Stmt::Block(StmtBlock::new_with_span(statements, x.body.span()).into())
498                }
499            }
500        }
501        // if expr1 { if expr2 { ... } } -> if expr1 && expr2 { ... }
502        Stmt::If(x, pos)
503            if x.branch.is_empty()
504                && x.body.len() == 1
505                && matches!(&x.body.as_ref()[0], Stmt::If(x2, ..) if x2.branch.is_empty()) =>
506        {
507            let Stmt::If(mut x2, ..) = x.body.as_mut()[0].take() else {
508                unreachable!()
509            };
510
511            state.set_dirty();
512            let body = x2.body.take_statements();
513            *x.body.statements_mut() =
514                optimize_stmt_block(body, state, preserve_result, true, false);
515
516            let mut expr2 = x2.expr.take();
517            optimize_expr(&mut expr2, state, false);
518
519            if let Expr::And(ref mut v, ..) = x.expr {
520                v.push(expr2);
521            } else {
522                let mut expr1 = x.expr.take();
523                optimize_expr(&mut expr1, state, false);
524
525                let mut v = StaticVec::new_const();
526                v.push(expr1);
527                v.push(expr2);
528
529                x.expr = Expr::And(v.into(), *pos);
530            }
531        }
532        // if expr { if_block } else { else_block }
533        Stmt::If(x, ..) => {
534            let FlowControl { expr, body, branch } = &mut **x;
535            optimize_expr(expr, state, false);
536            *body.statements_mut() =
537                optimize_stmt_block(body.take_statements(), state, preserve_result, true, false);
538            *branch.statements_mut() = optimize_stmt_block(
539                branch.take_statements(),
540                state,
541                preserve_result,
542                true,
543                false,
544            );
545        }
546
547        // switch const { ... }
548        Stmt::Switch(x, pos) if is_hashable_constant(&x.0) => {
549            let (
550                match_expr,
551                SwitchCasesCollection {
552                    expressions,
553                    cases,
554                    ranges,
555                    def_case,
556                },
557            ) = &mut **x;
558
559            let value = match_expr.get_literal_value(None).unwrap();
560            let hasher = &mut get_hasher();
561            value.hash(hasher);
562            let hash = hasher.finish();
563
564            // First check hashes
565            if let Some(case_blocks_list) = cases.get(&hash) {
566                match &case_blocks_list[..] {
567                    [] => (),
568                    [index] => {
569                        let mut b = mem::take(&mut expressions[*index]);
570                        cases.clear();
571
572                        if matches!(b.lhs, Expr::BoolConstant(true, ..)) {
573                            // Promote the matched case
574                            let mut statements = Stmt::Expr(b.rhs.take().into());
575                            optimize_stmt(&mut statements, state, true);
576                            *stmt = statements;
577                        } else {
578                            // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
579                            optimize_expr(&mut b.lhs, state, false);
580
581                            let branch = def_case.map_or(StmtBlock::NONE, |index| {
582                                let mut def_stmt = Stmt::Expr(expressions[index].rhs.take().into());
583                                optimize_stmt(&mut def_stmt, state, true);
584                                def_stmt.into()
585                            });
586                            let body = Stmt::Expr(b.rhs.take().into()).into();
587                            let expr = b.lhs.take();
588
589                            *stmt = Stmt::If(
590                                FlowControl { expr, body, branch }.into(),
591                                match_expr.start_position(),
592                            );
593                        }
594
595                        state.set_dirty();
596                        return;
597                    }
598                    _ => {
599                        for &index in case_blocks_list {
600                            let mut b = mem::take(&mut expressions[index]);
601
602                            if matches!(b.lhs, Expr::BoolConstant(true, ..)) {
603                                // Promote the matched case
604                                let mut statements = Stmt::Expr(b.rhs.take().into());
605                                optimize_stmt(&mut statements, state, true);
606                                *stmt = statements;
607                                state.set_dirty();
608                                return;
609                            }
610                        }
611                    }
612                }
613            }
614
615            // Then check ranges
616            if !ranges.is_empty() {
617                // Only one range or all ranges without conditions
618                if ranges.len() == 1
619                    || ranges
620                        .iter()
621                        .all(|r| matches!(expressions[r.index()].lhs, Expr::BoolConstant(true, ..)))
622                {
623                    if let Some(r) = ranges.iter().find(|r| r.contains(&value)) {
624                        let range_block = &mut expressions[r.index()];
625
626                        if matches!(range_block.lhs, Expr::BoolConstant(true, ..)) {
627                            // Promote the matched case
628                            let block = &mut expressions[r.index()];
629                            let mut statements = Stmt::Expr(block.rhs.take().into());
630                            optimize_stmt(&mut statements, state, true);
631                            *stmt = statements;
632                        } else {
633                            let mut expr = range_block.lhs.take();
634
635                            // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
636                            optimize_expr(&mut expr, state, false);
637
638                            let branch = def_case.map_or(StmtBlock::NONE, |index| {
639                                let mut def_stmt = Stmt::Expr(expressions[index].rhs.take().into());
640                                optimize_stmt(&mut def_stmt, state, true);
641                                def_stmt.into()
642                            });
643
644                            let body = Stmt::Expr(expressions[r.index()].rhs.take().into()).into();
645
646                            *stmt = Stmt::If(
647                                FlowControl { expr, body, branch }.into(),
648                                match_expr.start_position(),
649                            );
650                        }
651
652                        state.set_dirty();
653                        return;
654                    }
655                } else {
656                    // Multiple ranges - clear the table and just keep the right ranges
657                    if !cases.is_empty() {
658                        state.set_dirty();
659                        cases.clear();
660                    }
661
662                    let old_ranges_len = ranges.len();
663
664                    ranges.retain(|r| r.contains(&value));
665
666                    if ranges.len() != old_ranges_len {
667                        state.set_dirty();
668                    }
669
670                    for r in ranges {
671                        let b = &mut expressions[r.index()];
672                        optimize_expr(&mut b.lhs, state, false);
673                        optimize_expr(&mut b.rhs, state, false);
674                    }
675                    return;
676                }
677            }
678
679            // Promote the default case
680            state.set_dirty();
681
682            match def_case {
683                Some(index) => {
684                    let mut def_stmt = Stmt::Expr(expressions[*index].rhs.take().into());
685                    optimize_stmt(&mut def_stmt, state, true);
686                    *stmt = def_stmt;
687                }
688                _ => *stmt = Stmt::Block(StmtBlock::empty(*pos).into()),
689            }
690        }
691        // switch
692        Stmt::Switch(x, ..) => {
693            let (
694                match_expr,
695                SwitchCasesCollection {
696                    expressions,
697                    cases,
698                    ranges,
699                    def_case,
700                    ..
701                },
702            ) = &mut **x;
703
704            optimize_expr(match_expr, state, false);
705
706            // Optimize blocks
707            for b in &mut *expressions {
708                optimize_expr(&mut b.lhs, state, false);
709                optimize_expr(&mut b.rhs, state, false);
710
711                if matches!(b.lhs, Expr::BoolConstant(false, ..)) && !b.rhs.is_unit() {
712                    b.rhs = Expr::Unit(b.rhs.position());
713                    state.set_dirty();
714                }
715            }
716
717            // Remove false cases
718            cases.retain(|_, list| {
719                // Remove all entries that have false conditions
720                list.retain(|index| {
721                    if matches!(expressions[*index].lhs, Expr::BoolConstant(false, ..)) {
722                        state.set_dirty();
723                        false
724                    } else {
725                        true
726                    }
727                });
728                // Remove all entries after a `true` condition
729                if let Some(n) = list.iter().position(|&index| {
730                    matches!(expressions[index].lhs, Expr::BoolConstant(true, ..))
731                }) {
732                    if n + 1 < list.len() {
733                        state.set_dirty();
734                        list.truncate(n + 1);
735                    }
736                }
737                // Remove if no entry left
738                if list.is_empty() {
739                    state.set_dirty();
740                    false
741                } else {
742                    true
743                }
744            });
745
746            // Remove false ranges
747            ranges.retain(|r| {
748                if matches!(expressions[r.index()].lhs, Expr::BoolConstant(false, ..)) {
749                    state.set_dirty();
750                    false
751                } else {
752                    true
753                }
754            });
755
756            if let Some(index) = def_case {
757                optimize_expr(&mut expressions[*index].rhs, state, false);
758            }
759
760            // Remove unused block statements
761            expressions.iter_mut().enumerate().for_each(|(index, b)| {
762                if *def_case != Some(index)
763                    && cases.values().flat_map(|c| c.iter()).all(|&n| n != index)
764                    && ranges.iter().all(|r| r.index() != index)
765                    && !b.rhs.is_unit()
766                {
767                    b.rhs = Expr::Unit(b.rhs.position());
768                    state.set_dirty();
769                }
770            });
771        }
772
773        // while false { block } -> Noop
774        Stmt::While(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => match x.expr {
775            Expr::BoolConstant(false, pos) => {
776                state.set_dirty();
777                *stmt = Stmt::Noop(pos);
778            }
779            _ => unreachable!("`Expr::BoolConstant"),
780        },
781        // while expr { block }
782        Stmt::While(x, ..) => {
783            let FlowControl { expr, body, .. } = &mut **x;
784            optimize_expr(expr, state, false);
785            if let Expr::BoolConstant(true, pos) = expr {
786                *expr = Expr::Unit(*pos);
787            }
788            *body.statements_mut() =
789                optimize_stmt_block(body.take_statements(), state, false, true, false);
790        }
791        // do { block } while|until expr
792        Stmt::Do(x, ..) => {
793            optimize_expr(&mut x.expr, state, false);
794            *x.body.statements_mut() =
795                optimize_stmt_block(x.body.take_statements(), state, false, true, false);
796        }
797        // for id in expr { block }
798        Stmt::For(x, ..) => {
799            optimize_expr(&mut x.2.expr, state, false);
800            *x.2.body.statements_mut() =
801                optimize_stmt_block(x.2.body.take_statements(), state, false, true, false);
802        }
803        // let id = expr;
804        Stmt::Var(x, options, ..) if !options.intersects(ASTFlags::CONSTANT) => {
805            optimize_expr(&mut x.1, state, false);
806        }
807        // import expr as var;
808        #[cfg(not(feature = "no_module"))]
809        Stmt::Import(x, ..) => optimize_expr(&mut x.0, state, false),
810        // { block }
811        Stmt::Block(block) => {
812            let mut stmts =
813                optimize_stmt_block(block.take_statements(), state, preserve_result, true, false);
814
815            match stmts.as_mut_slice() {
816                [] => {
817                    state.set_dirty();
818                    *stmt = Stmt::Noop(block.span().start());
819                }
820                // Only one statement which is not block-dependent - promote
821                [s] if !s.is_block_dependent() => {
822                    state.set_dirty();
823                    *stmt = s.take();
824                }
825                _ => *block.statements_mut() = stmts,
826            }
827        }
828        // try { pure try_block } catch ( var ) { catch_block } -> try_block
829        Stmt::TryCatch(x, ..) if x.body.iter().all(Stmt::is_pure) => {
830            // If try block is pure, there will never be any exceptions
831            state.set_dirty();
832            let statements = x.body.take_statements();
833            let block = StmtBlock::new_with_span(
834                optimize_stmt_block(statements, state, false, true, false),
835                x.body.span(),
836            );
837            *stmt = Stmt::Block(block.into());
838        }
839        // try { try_block } catch ( var ) { catch_block }
840        Stmt::TryCatch(x, ..) => {
841            *x.body.statements_mut() =
842                optimize_stmt_block(x.body.take_statements(), state, false, true, false);
843            *x.branch.statements_mut() =
844                optimize_stmt_block(x.branch.take_statements(), state, false, true, false);
845        }
846
847        // expr(stmt)
848        Stmt::Expr(expr) if matches!(**expr, Expr::Stmt(..)) => {
849            state.set_dirty();
850            match expr.as_mut() {
851                Expr::Stmt(block) if !block.is_empty() => {
852                    let mut stmts_blk = mem::take(block.as_mut());
853                    *stmts_blk.statements_mut() =
854                        optimize_stmt_block(stmts_blk.take_statements(), state, true, true, false);
855                    *stmt = Stmt::Block(stmts_blk.into());
856                }
857                Expr::Stmt(..) => *stmt = Stmt::Noop(expr.position()),
858                _ => unreachable!("`Expr::Stmt`"),
859            }
860        }
861
862        // expr(func())
863        Stmt::Expr(expr) if matches!(**expr, Expr::FnCall(..)) => {
864            state.set_dirty();
865            match expr.take() {
866                Expr::FnCall(x, pos) => *stmt = Stmt::FnCall(x, pos),
867                _ => unreachable!(),
868            }
869        }
870
871        Stmt::Expr(expr) => optimize_expr(expr, state, false),
872
873        // func(...)
874        Stmt::FnCall(..) => match stmt.take() {
875            Stmt::FnCall(x, pos) => {
876                let mut expr = Expr::FnCall(x, pos);
877                optimize_expr(&mut expr, state, false);
878                *stmt = match expr {
879                    Expr::FnCall(x, pos) => Stmt::FnCall(x, pos),
880                    _ => Stmt::Expr(expr.into()),
881                }
882            }
883            _ => unreachable!(),
884        },
885
886        // break expr;
887        Stmt::BreakLoop(Some(ref mut expr), ..) => optimize_expr(expr, state, false),
888
889        // return expr;
890        Stmt::Return(Some(ref mut expr), ..) => optimize_expr(expr, state, false),
891
892        // Share nothing
893        #[cfg(not(feature = "no_closure"))]
894        Stmt::Share(x) if x.is_empty() => {
895            state.set_dirty();
896            *stmt = Stmt::Noop(Position::NONE);
897        }
898        // Share constants
899        #[cfg(not(feature = "no_closure"))]
900        Stmt::Share(x) => {
901            let orig_len = x.len();
902
903            if state.propagate_constants {
904                x.retain(|(v, _)| state.find_literal_constant(v.as_str()).is_none());
905
906                if x.len() != orig_len {
907                    state.set_dirty();
908                }
909            }
910        }
911
912        // All other statements - skip
913        _ => (),
914    }
915}
916
917// Convert a constant argument into [`Expr::DynamicConstant`].
918fn move_constant_arg(arg_expr: &mut Expr) -> bool {
919    match arg_expr {
920        Expr::DynamicConstant(..)
921        | Expr::Unit(..)
922        | Expr::StringConstant(..)
923        | Expr::CharConstant(..)
924        | Expr::BoolConstant(..)
925        | Expr::IntegerConstant(..) => false,
926        #[cfg(not(feature = "no_float"))]
927        Expr::FloatConstant(..) => false,
928
929        _ => arg_expr.get_literal_value(None).map_or(false, |value| {
930            *arg_expr = Expr::DynamicConstant(value.into(), arg_expr.start_position());
931            true
932        }),
933    }
934}
935
936/// Optimize an [expression][Expr].
937fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
938    // These keywords are handled specially
939    const DONT_EVAL_KEYWORDS: &[&str] = &[
940        KEYWORD_PRINT, // side effects
941        KEYWORD_DEBUG, // side effects
942        KEYWORD_EVAL,  // arbitrary scripts
943    ];
944
945    let start_pos = expr.position();
946
947    match expr {
948        // {}
949        Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) }
950        Expr::Stmt(x) if x.len() == 1 && matches!(x.statements()[0], Stmt::Expr(..)) => {
951            state.set_dirty();
952            match x.take_statements().remove(0) {
953                Stmt::Expr(mut e) => {
954                    optimize_expr(&mut e, state, false);
955                    *expr = *e;
956                }
957                _ => unreachable!("`Expr::Stmt`")
958            }
959        }
960        // { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
961        Expr::Stmt(x) => {
962            *x.statements_mut() = optimize_stmt_block(x.take_statements(), state, true, true, false);
963
964            // { Stmt(Expr) } - promote
965            if let [ Stmt::Expr(e) ] = x.statements_mut().as_mut() { state.set_dirty(); *expr = e.take(); }
966        }
967        // ()?.rhs
968        #[cfg(not(feature = "no_object"))]
969        Expr::Dot(x, options, ..) if options.intersects(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => {
970            state.set_dirty();
971            *expr = x.lhs.take();
972        }
973        // lhs.rhs
974        #[cfg(not(feature = "no_object"))]
975        Expr::Dot(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
976            // map.string
977            (Expr::Map(m, pos), Expr::Property(p, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => {
978                // Map literal where everything is pure - promote the indexed item.
979                // All other items can be thrown away.
980                state.set_dirty();
981                *expr = mem::take(&mut m.0).into_iter().find(|(x, ..)| x.name == p.2)
982                            .map_or_else(|| Expr::Unit(*pos), |(.., mut expr)| { expr.set_position(*pos); expr });
983            }
984            // var.property where var is a constant map
985            (Expr::Variable(v, _, pos) , Expr::Property(p, ..))  if state.propagate_constants && state.find_literal_constant(&v.1).map_or(false,Dynamic::is_map) => {
986                let v = state.find_literal_constant(&v.1).unwrap().as_map_ref().unwrap().get(p.2.as_str()).cloned().unwrap_or(Dynamic::UNIT);
987                *expr = Expr::from_dynamic(v, *pos);
988                state.set_dirty();
989            },
990            // var.rhs or this.rhs
991            (Expr::Variable(..) | Expr::ThisPtr(..), rhs) => optimize_expr(rhs, state, true),
992            // const.type_of()
993            (lhs, Expr::MethodCall(x, pos)) if lhs.is_constant() && x.name == KEYWORD_TYPE_OF && x.args.is_empty() => {
994                if let Some(value) = lhs.get_literal_value(None) {
995                    state.set_dirty();
996                    let typ = state.engine.map_type_name(value.type_name()).into();
997                    *expr = Expr::from_dynamic(typ, *pos);
998                }
999            }
1000            // const.is_shared()
1001            #[cfg(not(feature = "no_closure"))]
1002            (lhs, Expr::MethodCall(x, pos)) if lhs.is_constant() && x.name == crate::engine::KEYWORD_IS_SHARED && x.args.is_empty() => {
1003                if lhs.get_literal_value(None).is_some() {
1004                    state.set_dirty();
1005                    *expr = Expr::from_dynamic(Dynamic::FALSE, *pos);
1006                }
1007            }
1008            // lhs.rhs
1009            (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, true); }
1010        }
1011        // ....lhs.rhs
1012        #[cfg(not(feature = "no_object"))]
1013        Expr::Dot(x,..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); }
1014
1015        // ()?[rhs]
1016        #[cfg(not(feature = "no_index"))]
1017        Expr::Index(x, options, ..) if options.intersects(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => {
1018            state.set_dirty();
1019            *expr = x.lhs.take();
1020        }
1021        // lhs[rhs]
1022        #[cfg(not(feature = "no_index"))]
1023        Expr::Index(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
1024            // array[int]
1025            (Expr::Array(a, pos), Expr::IntegerConstant(i, ..)) if usize::try_from(*i).map(|x| x < a.len()).unwrap_or(false) && a.iter().all(Expr::is_pure) => {
1026                // Array literal where everything is pure - promote the indexed item.
1027                // All other items can be thrown away.
1028                state.set_dirty();
1029                let mut result = a[usize::try_from(*i).unwrap()].take();
1030                result.set_position(*pos);
1031                *expr = result;
1032            }
1033            // array[-int]
1034            (Expr::Array(a, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && usize::try_from(i.unsigned_abs()).map(|x| x <= a.len()).unwrap_or(false) && a.iter().all(Expr::is_pure) => {
1035                // Array literal where everything is pure - promote the indexed item.
1036                // All other items can be thrown away.
1037                state.set_dirty();
1038                let index = a.len() - usize::try_from(i.unsigned_abs()).unwrap();
1039                let mut result = a[index].take();
1040                result.set_position(*pos);
1041                *expr = result;
1042            }
1043            // map[string]
1044            (Expr::Map(m, pos), Expr::StringConstant(s, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => {
1045                // Map literal where everything is pure - promote the indexed item.
1046                // All other items can be thrown away.
1047                state.set_dirty();
1048                *expr = mem::take(&mut m.0).into_iter().find(|(x, ..)| x.name == s)
1049                            .map_or_else(|| Expr::Unit(*pos), |(.., mut expr)| { expr.set_position(*pos); expr });
1050            }
1051            #[cfg(not(feature = "no_object"))]
1052            (Expr::DynamicConstant(cst, pos ), Expr::StringConstant(s, ..)) if cst.is_map() => {
1053                // Constant map - promote the indexed item.
1054                state.set_dirty();
1055                let mut cst = mem::take(cst);
1056                *expr = cst.as_map_mut().unwrap()
1057                    .remove(s.as_str())
1058                    .map_or_else(
1059                        || Expr::Unit(*pos),
1060                        |v| Expr::from_dynamic(v, *pos),
1061                    );
1062            }
1063            // int[int]
1064            (Expr::IntegerConstant(n, pos), Expr::IntegerConstant(i, ..)) if usize::try_from(*i).map(|x| x < crate::INT_BITS).unwrap_or(false) => {
1065                // Bit-field literal indexing - get the bit
1066                state.set_dirty();
1067                *expr = Expr::BoolConstant((*n & (1 << usize::try_from(*i).unwrap())) != 0, *pos);
1068            }
1069            // int[-int]
1070            (Expr::IntegerConstant(n, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && usize::try_from(i.unsigned_abs()).map(|x| x <= crate::INT_BITS).unwrap_or(false) => {
1071                // Bit-field literal indexing - get the bit
1072                state.set_dirty();
1073                *expr = Expr::BoolConstant((*n & (1 << (crate::INT_BITS - usize::try_from(i.unsigned_abs()).unwrap()))) != 0, *pos);
1074            }
1075            // string[int]
1076            (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, ..)) if usize::try_from(*i).map(|x| x < s.chars().count()).unwrap_or(false) => {
1077                // String literal indexing - get the character
1078                state.set_dirty();
1079                *expr = Expr::CharConstant(s.chars().nth(usize::try_from(*i).unwrap()).unwrap(), *pos);
1080            }
1081            // string[-int]
1082            (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && usize::try_from(i.unsigned_abs()).map(|x| x <= s.chars().count()).unwrap_or(false) => {
1083                // String literal indexing - get the character
1084                state.set_dirty();
1085                *expr = Expr::CharConstant(s.chars().rev().nth(usize::try_from(i.unsigned_abs()).unwrap() - 1).unwrap(), *pos);
1086            }
1087            // var[property] where var is a constant map variable
1088            #[cfg(not(feature = "no_object"))]
1089            (Expr::Variable(v, _, pos) , Expr::StringConstant(s, ..))  if state.propagate_constants && state.find_literal_constant(&v.1).map_or(false, Dynamic::is_map) => {
1090                let v = state.find_literal_constant(&v.1).unwrap().as_map_ref().unwrap().get(s.as_str()).cloned().unwrap_or(Dynamic::UNIT);
1091                *expr = Expr::from_dynamic(v, *pos);
1092                state.set_dirty();
1093            },
1094            // var[rhs] or this[rhs]
1095            (Expr::Variable(..) | Expr::ThisPtr(..), rhs) => optimize_expr(rhs, state, true),
1096            // lhs[rhs]
1097            (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, true); }
1098        },
1099        // ...[lhs][rhs]
1100        #[cfg(not(feature = "no_index"))]
1101        Expr::Index(x, ..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); }
1102        // ``
1103        Expr::InterpolatedString(x, pos) if x.is_empty() => {
1104            state.set_dirty();
1105            *expr = Expr::StringConstant(state.engine.const_empty_string(), *pos);
1106        }
1107        // `... ${const} ...`
1108        Expr::InterpolatedString(..) if expr.is_constant() => {
1109            state.set_dirty();
1110            *expr = Expr::StringConstant(expr.get_literal_value(None).unwrap().cast::<ImmutableString>(), expr.position());
1111        }
1112        // `... ${ ... } ...`
1113        Expr::InterpolatedString(x, ..) => {
1114            x.iter_mut().for_each(|expr| optimize_expr(expr, state, false));
1115
1116            let mut n = 0;
1117
1118            // Merge consecutive strings
1119            while n < x.len() - 1 {
1120                match (x[n].take(),x[n+1].take()) {
1121                    (Expr::StringConstant(mut s1, pos), Expr::StringConstant(s2, ..)) => { s1 += s2; x[n] = Expr::StringConstant(s1, pos); x.remove(n+1); state.set_dirty(); }
1122                    (expr1, Expr::Unit(..)) => { x[n] = expr1; x.remove(n+1); state.set_dirty(); }
1123                    (Expr::Unit(..), expr2) => { x[n+1] = expr2; x.remove(n); state.set_dirty(); }
1124                    (expr1, Expr::StringConstant(s, ..)) if s.is_empty() => { x[n] = expr1; x.remove(n+1); state.set_dirty(); }
1125                    (Expr::StringConstant(s, ..), expr2) if s.is_empty()=> { x[n+1] = expr2; x.remove(n); state.set_dirty(); }
1126                    (expr1, expr2) => { x[n] = expr1; x[n+1] = expr2; n += 1; }
1127                }
1128            }
1129
1130            x.shrink_to_fit();
1131        }
1132        // [ constant .. ]
1133        #[cfg(not(feature = "no_index"))]
1134        Expr::Array(..) if expr.is_constant() => {
1135            state.set_dirty();
1136            *expr = Expr::DynamicConstant(expr.get_literal_value(None).unwrap().into(), expr.position());
1137        }
1138        // [ items .. ]
1139        #[cfg(not(feature = "no_index"))]
1140        Expr::Array(x, ..) => x.iter_mut().for_each(|expr| optimize_expr(expr, state, false)),
1141        // #{ key:constant, .. }
1142        #[cfg(not(feature = "no_object"))]
1143        Expr::Map(..) if expr.is_constant() => {
1144            state.set_dirty();
1145            *expr = Expr::DynamicConstant(expr.get_literal_value(None).unwrap().into(), expr.position());
1146        }
1147        // #{ key:value, .. }
1148        #[cfg(not(feature = "no_object"))]
1149        Expr::Map(x, ..) => x.0.iter_mut().for_each(|(.., expr)| optimize_expr(expr, state, false)),
1150        // lhs && rhs
1151        Expr::And(x, ..) => {
1152            let mut is_false = None;
1153            let mut n = 0 ;
1154
1155            while n < x.len() {
1156                match x[n] {
1157                    // true && rhs -> rhs
1158                    Expr::BoolConstant(true, ..) => { state.set_dirty(); x.remove(n); }
1159                    // false && rhs -> false
1160                    Expr::BoolConstant(false, pos) => {
1161                        if x.iter().take(n).all(Expr::is_pure) {
1162                            is_false = Some(pos);
1163                            state.set_dirty();
1164                        }
1165                        if x.len() > n + 1 {
1166                            x.truncate(n + 1);
1167                            state.set_dirty();
1168                        }
1169                        break;
1170                    }
1171                    _ => { optimize_expr(&mut x[n], state, false); n += 1; }
1172                }
1173            }
1174
1175            if let Some(pos) = is_false {
1176                state.set_dirty();
1177                *expr = Expr::BoolConstant(false, pos);
1178            } else if x.is_empty() {
1179                state.set_dirty();
1180                *expr = Expr::BoolConstant(true, start_pos);
1181            } else if x.len() == 1 {
1182                state.set_dirty();
1183                *expr = x[0].take();
1184            }
1185        },
1186        // lhs || rhs
1187        Expr::Or(ref mut x, ..) => {
1188            let mut is_true = None;
1189            let mut n = 0 ;
1190
1191            while n < x.len() {
1192                match x[n] {
1193                    // false || rhs -> rhs
1194                    Expr::BoolConstant(false, ..) => { state.set_dirty(); x.remove(n); }
1195                    // true || rhs -> true
1196                    Expr::BoolConstant(true, pos) => {
1197                        if x.iter().take(n).all(Expr::is_pure) {
1198                            is_true = Some(pos);
1199                            state.set_dirty();
1200                        }
1201                        if x.len() > n + 1 {
1202                            x.truncate(n + 1);
1203                            state.set_dirty();
1204                        }
1205                        break;
1206                    }
1207                    _ => { optimize_expr(&mut x[n], state, false); n += 1; }
1208                }
1209            }
1210
1211            if let Some(pos) = is_true {
1212                state.set_dirty();
1213                *expr = Expr::BoolConstant(true, pos);
1214            } else if x.is_empty() {
1215                state.set_dirty();
1216                *expr = Expr::BoolConstant(false, start_pos);
1217            } else if x.len() == 1 {
1218                state.set_dirty();
1219                *expr = x[0].take();
1220            }
1221        },
1222        // () ?? rhs -> rhs
1223        Expr::Coalesce(x, ..) => {
1224            let mut n = 0 ;
1225
1226            while n < x.len() {
1227                match &x[n] {
1228                    // () ?? rhs -> rhs
1229                    Expr::Unit(..) => { state.set_dirty(); x.remove(n); }
1230                    e if e.is_constant() => {
1231                        if x.len() > n + 1 {
1232                            x.truncate(n + 1);
1233                            state.set_dirty();
1234                        }
1235                        break;
1236                    }
1237                    _ => { optimize_expr(&mut x[n], state, false); n += 1; }
1238                }
1239            }
1240
1241            if x.is_empty() {
1242                state.set_dirty();
1243                *expr = Expr::BoolConstant(false, start_pos);
1244            } else if x.len() == 1 {
1245                state.set_dirty();
1246                *expr = x[0].take();
1247            }
1248        },
1249
1250        // !true or !false
1251        Expr::FnCall(x,..)
1252            if x.name == OP_NOT
1253            && x.args.len() == 1
1254            && matches!(x.args[0], Expr::BoolConstant(..))
1255        => {
1256            state.set_dirty();
1257            match x.args[0] {
1258                Expr::BoolConstant(b, pos) => *expr = Expr::BoolConstant(!b, pos),
1259                _ => unreachable!(),
1260            }
1261        }
1262
1263        // nnn::id(args ..) -> optimize function call arguments
1264        #[cfg(not(feature = "no_module"))]
1265        Expr::FnCall(x, ..) if x.is_qualified() => x.args.iter_mut().for_each(|arg_expr| {
1266            optimize_expr(arg_expr, state, false);
1267
1268            if move_constant_arg(arg_expr) {
1269                state.set_dirty();
1270            }
1271        }),
1272        // eval!
1273        Expr::FnCall(x, ..) if x.name == KEYWORD_EVAL => {
1274            state.propagate_constants = false;
1275        }
1276        // Fn
1277        Expr::FnCall(x, pos) if x.args.len() == 1 && x.name == KEYWORD_FN_PTR && x.constant_args() => {
1278            let fn_name = match x.args[0] {
1279                Expr::StringConstant(ref s, ..) => s.clone().into(),
1280                _ => Dynamic::UNIT
1281            };
1282
1283            if let Ok(fn_ptr) = fn_name.into_immutable_string().map_err(Into::into).and_then(FnPtr::try_from) {
1284                state.set_dirty();
1285                *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
1286            } else {
1287                optimize_expr(&mut x.args[0], state, false);
1288            }
1289        }
1290        // curry(FnPtr, constants...)
1291        Expr::FnCall(x, pos) if x.args.len() >= 2
1292                                && x.name == KEYWORD_FN_PTR_CURRY
1293                                && matches!(x.args[0], Expr::DynamicConstant(ref v, ..) if v.is_fnptr())
1294                                && x.constant_args()
1295        => {
1296            let mut fn_ptr = x.args[0].get_literal_value(None).unwrap().cast::<FnPtr>();
1297            fn_ptr.extend(x.args.iter().skip(1).map(|arg_expr| arg_expr.get_literal_value(None).unwrap()));
1298            state.set_dirty();
1299            *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
1300        }
1301
1302        // Do not call some special keywords that may have side effects
1303        Expr::FnCall(x, ..) if DONT_EVAL_KEYWORDS.contains(&x.name.as_str()) => {
1304            x.args.iter_mut().for_each(|arg_expr| optimize_expr(arg_expr, state, false));
1305        }
1306
1307        // Call built-in operators
1308        Expr::FnCall(x, pos) if state.optimization_level == OptimizationLevel::Simple // simple optimizations
1309                                && x.constant_args() // all arguments are constants
1310        => {
1311            let arg_values = &mut x.args.iter().map(|arg_expr| arg_expr.get_literal_value(None).unwrap()).collect::<FnArgsVec<_>>();
1312            let arg_types = arg_values.iter().map(Dynamic::type_id).collect::<FnArgsVec<_>>();
1313
1314            match x.name.as_str() {
1315                KEYWORD_TYPE_OF if arg_values.len() == 1 => {
1316                    state.set_dirty();
1317                    let typ = state.engine.map_type_name(arg_values[0].type_name()).into();
1318                    *expr = Expr::from_dynamic(typ, *pos);
1319                    return;
1320                }
1321                #[cfg(not(feature = "no_closure"))]
1322                crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => {
1323                    state.set_dirty();
1324                    *expr = Expr::from_dynamic(Dynamic::FALSE, *pos);
1325                    return;
1326                }
1327                // Overloaded operators can override built-in.
1328                _ if x.args.len() == 2 && x.is_operator_call() && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => {
1329                    if let Some((f, ctx)) = get_builtin_binary_op_fn(x.op_token.as_ref().unwrap(), &arg_values[0], &arg_values[1]) {
1330                        let context = ctx.then(|| (state.engine, x.name.as_str(), None, &state.global, *pos).into());
1331                        let (first, second) = arg_values.split_first_mut().unwrap();
1332
1333                        if let Ok(result) = f(context, &mut [ first, &mut second[0] ]) {
1334                            state.set_dirty();
1335                            *expr = Expr::from_dynamic(result, *pos);
1336                            return;
1337                        }
1338                    }
1339                }
1340                _ => ()
1341            }
1342
1343            x.args.iter_mut().for_each(|arg_expr| {
1344                optimize_expr(arg_expr, state, false);
1345                if move_constant_arg(arg_expr) {
1346                    state.set_dirty();
1347                }
1348            });
1349        }
1350
1351        // Eagerly call functions
1352        Expr::FnCall(x, pos) if state.optimization_level == OptimizationLevel::Full // full optimizations
1353                                && x.constant_args() // all arguments are constants
1354        => {
1355            // First search for script-defined functions (can override built-in)
1356            let _has_script_fn = false;
1357            #[cfg(not(feature = "no_function"))]
1358            let _has_script_fn = !x.hashes.is_native_only() && state.global.lib.iter().find_map(|m| m.get_script_fn(&x.name, x.args.len())).is_some();
1359
1360            if !_has_script_fn {
1361                let arg_values = &mut x.args.iter().map(|a| a.get_literal_value(None)).collect::<Option<FnArgsVec<_>>>().unwrap();
1362
1363                let result = match x.name.as_str() {
1364                    KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()),
1365                    #[cfg(not(feature = "no_closure"))]
1366                    crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE),
1367                    _ => state.call_fn_with_const_args(&x.name, x.op_token.as_ref(), arg_values)
1368                };
1369
1370                if let Some(r) = result {
1371                    state.set_dirty();
1372                    *expr = Expr::from_dynamic(r, *pos);
1373                    return;
1374                }
1375            }
1376
1377            x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
1378        }
1379
1380        // id(args ..) or xxx.id(args ..) -> optimize function call arguments
1381        Expr::FnCall(x, ..) | Expr::MethodCall(x, ..) => x.args.iter_mut().for_each(|arg_expr| {
1382            optimize_expr(arg_expr, state, false);
1383            if move_constant_arg(arg_expr) {
1384                state.set_dirty();
1385            }
1386        }),
1387
1388        // constant-name
1389        #[cfg(not(feature = "no_module"))]
1390        Expr::Variable(x, ..) if !x.2.is_empty() => (),
1391        Expr::Variable(x, .., pos) if state.propagate_constants && state.find_literal_constant(&x.1).is_some() => {
1392            // Replace constant with value
1393            *expr = Expr::from_dynamic(state.find_literal_constant(&x.1).unwrap().clone(), *pos);
1394            state.set_dirty();
1395        }
1396
1397        // Custom syntax
1398        #[cfg(not(feature = "no_custom_syntax"))]
1399        Expr::Custom(x, ..) => {
1400            if x.scope_may_be_changed {
1401                state.propagate_constants = false;
1402            }
1403            // Do not optimize custom syntax expressions as you won't know how they would be called
1404        }
1405
1406        // All other expressions - skip
1407        _ => (),
1408    }
1409}
1410
1411impl Engine {
1412    /// Has a system function a Rust-native override?
1413    fn has_native_fn_override(&self, hash_script: u64, arg_types: impl AsRef<[TypeId]>) -> bool {
1414        let hash = calc_fn_hash_full(hash_script, arg_types.as_ref().iter().copied());
1415
1416        // First check the global namespace and packages, but skip modules that are standard because
1417        // they should never conflict with system functions.
1418        if self
1419            .global_modules
1420            .iter()
1421            .filter(|m| !m.is_standard_lib())
1422            .any(|m| m.contains_fn(hash))
1423        {
1424            return true;
1425        }
1426
1427        // Then check sub-modules
1428        #[cfg(not(feature = "no_module"))]
1429        if self
1430            .global_sub_modules
1431            .values()
1432            .any(|m| m.contains_qualified_fn(hash))
1433        {
1434            return true;
1435        }
1436
1437        false
1438    }
1439
1440    /// Optimize a block of [statements][Stmt] at top level.
1441    ///
1442    /// Constants and variables from the scope are added.
1443    fn optimize_top_level(
1444        &self,
1445        statements: StmtBlockContainer,
1446        scope: Option<&Scope>,
1447        lib: &[crate::SharedModule],
1448        optimization_level: OptimizationLevel,
1449    ) -> StmtBlockContainer {
1450        let mut statements = statements;
1451
1452        // If optimization level is None then skip optimizing
1453        if optimization_level == OptimizationLevel::None {
1454            statements.shrink_to_fit();
1455            return statements;
1456        }
1457
1458        // Set up the state
1459        let mut state = OptimizerState::new(self, lib, scope, optimization_level);
1460
1461        // Add constants from global modules
1462        self.global_modules
1463            .iter()
1464            .rev()
1465            .flat_map(|m| m.iter_var())
1466            .for_each(|(name, value)| state.push_var(name.into(), Some(Cow::Borrowed(value))));
1467
1468        // Add constants and variables from the scope
1469        state
1470            .scope
1471            .into_iter()
1472            .flat_map(Scope::iter_inner)
1473            .for_each(|(name, constant, value)| {
1474                state.push_var(
1475                    name.into(),
1476                    if constant {
1477                        Some(Cow::Borrowed(value))
1478                    } else {
1479                        None
1480                    },
1481                );
1482            });
1483
1484        optimize_stmt_block(statements, &mut state, true, false, true)
1485    }
1486
1487    /// Optimize a collection of statements and functions into an [`AST`].
1488    pub(crate) fn optimize_into_ast(
1489        &self,
1490        scope: Option<&Scope>,
1491        statements: StmtBlockContainer,
1492        #[cfg(not(feature = "no_function"))] functions: impl IntoIterator<Item = crate::Shared<crate::ast::ScriptFuncDef>>
1493            + AsRef<[crate::Shared<crate::ast::ScriptFuncDef>]>,
1494        optimization_level: OptimizationLevel,
1495    ) -> AST {
1496        let mut statements = statements;
1497
1498        #[cfg(not(feature = "no_function"))]
1499        let lib: crate::Shared<_> = if optimization_level == OptimizationLevel::None {
1500            crate::Module::from(functions).into()
1501        } else {
1502            // We only need the script library's signatures for optimization purposes
1503            let lib2 = crate::Module::from(
1504                functions
1505                    .as_ref()
1506                    .iter()
1507                    .map(|fn_def| fn_def.clone_function_signatures().into()),
1508            );
1509
1510            let lib2 = &[lib2.into()];
1511
1512            crate::Module::from(functions.into_iter().map(|fn_def| {
1513                // Optimize the function body
1514                let mut fn_def = crate::func::shared_take_or_clone(fn_def);
1515                let statements = fn_def.body.take_statements();
1516                *fn_def.body.statements_mut() =
1517                    self.optimize_top_level(statements, scope, lib2, optimization_level);
1518                fn_def.into()
1519            }))
1520            .into()
1521        };
1522        #[cfg(feature = "no_function")]
1523        let lib: crate::Shared<_> = crate::Module::new().into();
1524
1525        statements.shrink_to_fit();
1526
1527        AST::new(
1528            match optimization_level {
1529                OptimizationLevel::None => statements,
1530                OptimizationLevel::Simple | OptimizationLevel::Full => self.optimize_top_level(
1531                    statements,
1532                    scope,
1533                    std::slice::from_ref(&lib),
1534                    optimization_level,
1535                ),
1536            },
1537            #[cfg(not(feature = "no_function"))]
1538            lib,
1539        )
1540    }
1541}