1#![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#[derive(Debug, Eq, PartialEq, Clone, Copy, Default, Hash)]
34#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
35#[non_exhaustive]
36pub enum OptimizationLevel {
37 None,
39 #[default]
41 Simple,
42 Full,
45}
46
47#[derive(Debug, Clone)]
49struct OptimizerState<'a> {
50 is_dirty: bool,
52 variables: Vec<(ImmutableString, Option<Cow<'a, Dynamic>>)>,
54 propagate_constants: bool,
56 engine: &'a Engine,
58 scope: Option<&'a Scope<'a>>,
60 global: GlobalRuntimeState,
62 caches: Caches,
64 optimization_level: OptimizationLevel,
66}
67
68impl<'a> OptimizerState<'a> {
69 #[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 #[inline(always)]
98 pub fn set_dirty(&mut self) {
99 self.is_dirty = true;
100 }
101 #[inline(always)]
103 pub fn clear_dirty(&mut self) {
104 self.is_dirty = false;
105 }
106 #[inline(always)]
108 pub const fn is_dirty(&self) -> bool {
109 self.is_dirty
110 }
111 #[inline(always)]
113 pub fn rewind_var(&mut self, len: usize) {
114 self.variables.truncate(len);
115 }
116 #[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 #[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 #[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
157fn 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 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 loop {
199 state.clear_dirty();
200
201 let orig_constants_len = state.variables.len(); let orig_propagate_constants = state.propagate_constants;
203
204 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 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 Some(Cow::Owned(x.1.get_literal_value(None).unwrap()))
228 } else {
229 None
231 };
232 state.push_var(x.0.name.clone(), value);
233 }
234 _ => optimize_stmt(stmt, state, preserve_result),
236 }
237 });
238
239 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 if preserve_result {
280 loop {
281 match statements[..] {
282 [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 [.., 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 [.., 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 [.., ref second_last_stmt, Stmt::Noop(..)]
313 if second_last_stmt.returns_value() =>
314 {
315 break
316 }
317 [.., 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 [.., Stmt::Return(None, options, ..)]
341 if reduce_return && !options.intersects(ASTFlags::BREAK) =>
342 {
343 state.set_dirty();
344 statements.pop().unwrap();
345 }
346 [.., 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 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#[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
401fn 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 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 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 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 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 Stmt::Expr(expr.into())
465 };
466 }
467 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 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 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 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 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 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 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 let mut statements = Stmt::Expr(b.rhs.take().into());
575 optimize_stmt(&mut statements, state, true);
576 *stmt = statements;
577 } else {
578 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 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 if !ranges.is_empty() {
617 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 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 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 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 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 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 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 cases.retain(|_, list| {
719 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 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 if list.is_empty() {
739 state.set_dirty();
740 false
741 } else {
742 true
743 }
744 });
745
746 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 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 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 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 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 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 Stmt::Var(x, options, ..) if !options.intersects(ASTFlags::CONSTANT) => {
805 optimize_expr(&mut x.1, state, false);
806 }
807 #[cfg(not(feature = "no_module"))]
809 Stmt::Import(x, ..) => optimize_expr(&mut x.0, state, false),
810 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 [s] if !s.is_block_dependent() => {
822 state.set_dirty();
823 *stmt = s.take();
824 }
825 _ => *block.statements_mut() = stmts,
826 }
827 }
828 Stmt::TryCatch(x, ..) if x.body.iter().all(Stmt::is_pure) => {
830 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 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 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 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 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 Stmt::BreakLoop(Some(ref mut expr), ..) => optimize_expr(expr, state, false),
888
889 Stmt::Return(Some(ref mut expr), ..) => optimize_expr(expr, state, false),
891
892 #[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 #[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 _ => (),
914 }
915}
916
917fn 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
936fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
938 const DONT_EVAL_KEYWORDS: &[&str] = &[
940 KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_EVAL, ];
944
945 let start_pos = expr.position();
946
947 match expr {
948 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 Expr::Stmt(x) => {
962 *x.statements_mut() = optimize_stmt_block(x.take_statements(), state, true, true, false);
963
964 if let [ Stmt::Expr(e) ] = x.statements_mut().as_mut() { state.set_dirty(); *expr = e.take(); }
966 }
967 #[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 #[cfg(not(feature = "no_object"))]
975 Expr::Dot(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
976 (Expr::Map(m, pos), Expr::Property(p, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => {
978 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 (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 (Expr::Variable(..) | Expr::ThisPtr(..), rhs) => optimize_expr(rhs, state, true),
992 (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 #[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) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, true); }
1010 }
1011 #[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 #[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 #[cfg(not(feature = "no_index"))]
1023 Expr::Index(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
1024 (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 state.set_dirty();
1029 let mut result = a[usize::try_from(*i).unwrap()].take();
1030 result.set_position(*pos);
1031 *expr = result;
1032 }
1033 (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 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 (Expr::Map(m, pos), Expr::StringConstant(s, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => {
1045 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 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 (Expr::IntegerConstant(n, pos), Expr::IntegerConstant(i, ..)) if usize::try_from(*i).map(|x| x < crate::INT_BITS).unwrap_or(false) => {
1065 state.set_dirty();
1067 *expr = Expr::BoolConstant((*n & (1 << usize::try_from(*i).unwrap())) != 0, *pos);
1068 }
1069 (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 state.set_dirty();
1073 *expr = Expr::BoolConstant((*n & (1 << (crate::INT_BITS - usize::try_from(i.unsigned_abs()).unwrap()))) != 0, *pos);
1074 }
1075 (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, ..)) if usize::try_from(*i).map(|x| x < s.chars().count()).unwrap_or(false) => {
1077 state.set_dirty();
1079 *expr = Expr::CharConstant(s.chars().nth(usize::try_from(*i).unwrap()).unwrap(), *pos);
1080 }
1081 (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 state.set_dirty();
1085 *expr = Expr::CharConstant(s.chars().rev().nth(usize::try_from(i.unsigned_abs()).unwrap() - 1).unwrap(), *pos);
1086 }
1087 #[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 (Expr::Variable(..) | Expr::ThisPtr(..), rhs) => optimize_expr(rhs, state, true),
1096 (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, true); }
1098 },
1099 #[cfg(not(feature = "no_index"))]
1101 Expr::Index(x, ..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); }
1102 Expr::InterpolatedString(x, pos) if x.is_empty() => {
1104 state.set_dirty();
1105 *expr = Expr::StringConstant(state.engine.const_empty_string(), *pos);
1106 }
1107 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 Expr::InterpolatedString(x, ..) => {
1114 x.iter_mut().for_each(|expr| optimize_expr(expr, state, false));
1115
1116 let mut n = 0;
1117
1118 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 #[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 #[cfg(not(feature = "no_index"))]
1140 Expr::Array(x, ..) => x.iter_mut().for_each(|expr| optimize_expr(expr, state, false)),
1141 #[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 #[cfg(not(feature = "no_object"))]
1149 Expr::Map(x, ..) => x.0.iter_mut().for_each(|(.., expr)| optimize_expr(expr, state, false)),
1150 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 Expr::BoolConstant(true, ..) => { state.set_dirty(); x.remove(n); }
1159 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 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 Expr::BoolConstant(false, ..) => { state.set_dirty(); x.remove(n); }
1195 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 Expr::Coalesce(x, ..) => {
1224 let mut n = 0 ;
1225
1226 while n < x.len() {
1227 match &x[n] {
1228 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 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 #[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 Expr::FnCall(x, ..) if x.name == KEYWORD_EVAL => {
1274 state.propagate_constants = false;
1275 }
1276 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 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 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 Expr::FnCall(x, pos) if state.optimization_level == OptimizationLevel::Simple && x.constant_args() => {
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 _ 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 Expr::FnCall(x, pos) if state.optimization_level == OptimizationLevel::Full && x.constant_args() => {
1355 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 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 #[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 *expr = Expr::from_dynamic(state.find_literal_constant(&x.1).unwrap().clone(), *pos);
1394 state.set_dirty();
1395 }
1396
1397 #[cfg(not(feature = "no_custom_syntax"))]
1399 Expr::Custom(x, ..) => {
1400 if x.scope_may_be_changed {
1401 state.propagate_constants = false;
1402 }
1403 }
1405
1406 _ => (),
1408 }
1409}
1410
1411impl Engine {
1412 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 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 #[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 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 == OptimizationLevel::None {
1454 statements.shrink_to_fit();
1455 return statements;
1456 }
1457
1458 let mut state = OptimizerState::new(self, lib, scope, optimization_level);
1460
1461 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 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 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 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 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}