1use std::{collections::HashMap, mem::swap};
2
3use rustc_hash::FxHashMap;
4use swc_common::{pass::Either, util::take::Take, Span, Spanned, SyntaxContext, DUMMY_SP};
5use swc_ecma_ast::*;
6use swc_ecma_utils::{contains_arguments, contains_this_expr, find_pat_ids, ExprFactory};
7use swc_ecma_visit::{noop_visit_type, Visit, VisitMutWith, VisitWith};
8
9use super::{util::NormalMultiReplacer, BitCtx, Optimizer};
10#[cfg(feature = "debug")]
11use crate::debug::dump;
12use crate::{
13 program_data::{ProgramData, ScopeData, VarUsageInfoFlags},
14 util::{idents_captured_by, make_number},
15};
16
17impl Optimizer<'_> {
19 pub(super) fn negate_iife_ignoring_ret(&mut self, e: &mut Expr) {
21 if !self.options.negate_iife
22 || self
23 .ctx
24 .bit_ctx
25 .intersects(BitCtx::InBangArg | BitCtx::DontUseNegatedIife)
26 {
27 return;
28 }
29
30 let expr = match e {
31 Expr::Call(e) => e,
32 _ => return,
33 };
34
35 let callee = match &mut expr.callee {
36 Callee::Super(_) | Callee::Import(_) => return,
37 Callee::Expr(e) => &mut **e,
38 #[cfg(swc_ast_unknown)]
39 _ => panic!("unable to access unknown nodes"),
40 };
41
42 if let Expr::Fn(..) = callee {
43 report_change!("negate_iife: Negating iife");
44 *e = UnaryExpr {
45 span: DUMMY_SP,
46 op: op!("!"),
47 arg: Box::new(e.take()),
48 }
49 .into();
50 }
51 }
52
53 pub(super) fn negate_iife_in_cond(&mut self, e: &mut Expr) -> bool {
58 let cond = match e {
59 Expr::Cond(v) => v,
60 _ => return false,
61 };
62
63 let test_call = match &mut *cond.test {
64 Expr::Call(e) => e,
65 _ => return false,
66 };
67
68 let callee = match &mut test_call.callee {
69 Callee::Super(_) | Callee::Import(_) => return false,
70 Callee::Expr(e) => &mut **e,
71 #[cfg(swc_ast_unknown)]
72 _ => panic!("unable to access unknown nodes"),
73 };
74
75 match callee {
76 Expr::Fn(..) => {
77 report_change!("negate_iife: Swapping cons and alt");
78 cond.test = UnaryExpr {
79 span: DUMMY_SP,
80 op: op!("!"),
81 arg: cond.test.take(),
82 }
83 .into();
84 swap(&mut cond.cons, &mut cond.alt);
85 true
86 }
87 _ => false,
88 }
89 }
90
91 pub(super) fn restore_negated_iife(&mut self, cond: &mut CondExpr) {
92 if !self.ctx.bit_ctx.contains(BitCtx::DontUseNegatedIife) {
93 return;
94 }
95
96 if let Expr::Unary(UnaryExpr {
97 op: op!("!"), arg, ..
98 }) = &mut *cond.test
99 {
100 if let Expr::Call(CallExpr {
101 span: call_span,
102 callee: Callee::Expr(callee),
103 args,
104 ..
105 }) = &mut **arg
106 {
107 if let Expr::Fn(..) = &**callee {
108 cond.test = CallExpr {
109 span: *call_span,
110 callee: callee.take().as_callee(),
111 args: args.take(),
112 ..Default::default()
113 }
114 .into();
115 swap(&mut cond.cons, &mut cond.alt);
116 }
117 }
118 };
119 }
120}
121
122impl Optimizer<'_> {
124 #[cfg_attr(feature = "debug", tracing::instrument(level = "debug", skip(self, e)))]
147 pub(super) fn inline_args_of_iife(&mut self, e: &mut CallExpr) {
148 if self.options.inline == 0 && !self.options.reduce_vars && !self.options.reduce_fns {
149 return;
150 }
151
152 let has_spread_arg = e.args.iter().any(|v| v.spread.is_some());
153 if has_spread_arg {
154 return;
155 }
156
157 let callee = match &mut e.callee {
158 Callee::Super(_) | Callee::Import(_) => return,
159 Callee::Expr(e) => &mut **e,
160 #[cfg(swc_ast_unknown)]
161 _ => panic!("unable to access unknown nodes"),
162 };
163
164 if let Some(scope) = find_scope(self.data, callee) {
165 if scope.contains(ScopeData::USED_ARGUMENTS) {
166 log_abort!("iife: [x] Found usage of arguments");
167 return;
168 }
169 }
170
171 fn clean_params(callee: &mut Expr) {
172 match callee {
173 Expr::Arrow(callee) => {
174 callee.params.retain(|p| !p.is_invalid())
176 }
177 Expr::Fn(callee) => {
178 callee.function.params.retain(|p| !p.pat.is_invalid())
180 }
181 _ => {}
182 }
183 }
184
185 if let Expr::Fn(FnExpr {
186 ident: Some(ident), ..
187 }) = callee
188 {
189 if self
190 .data
191 .vars
192 .get(&ident.to_id())
193 .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY))
194 .is_some()
195 {
196 log_abort!("iife: [x] Recursive?");
197 return;
198 }
199 }
200
201 let params = find_params(callee);
202 if let Some(mut params) = params {
203 let mut vars = HashMap::default();
204 for (idx, param) in params.iter_mut().enumerate() {
206 match &mut **param {
207 Pat::Ident(param) => {
208 if param.sym == "arguments" {
209 continue;
210 }
211 if let Some(usage) = self.data.vars.get(¶m.to_id()) {
212 if usage.flags.contains(VarUsageInfoFlags::REASSIGNED) {
213 continue;
214 }
215 }
216
217 let arg = e.args.get(idx).map(|v| &v.expr);
218
219 if let Some(arg) = arg {
220 match &**arg {
221 Expr::Lit(Lit::Regex(..)) => continue,
222 Expr::Lit(Lit::Str(s)) if s.value.len() > 3 => continue,
223 Expr::Lit(..) => {}
224 _ => continue,
225 }
226
227 let should_be_inlined = self.can_be_inlined_for_iife(arg);
228 if should_be_inlined {
229 trace_op!(
230 "iife: Trying to inline argument ({}{:?})",
231 param.id.sym,
232 param.id.ctxt
233 );
234 vars.insert(param.to_id(), arg.clone());
235 } else {
236 trace_op!(
237 "iife: Trying to inline argument ({}{:?}) (not inlinable)",
238 param.id.sym,
239 param.id.ctxt
240 );
241 }
242 } else {
243 trace_op!(
244 "iife: Trying to inline argument ({}{:?}) (undefined)",
245 param.id.sym,
246 param.id.ctxt
247 );
248
249 vars.insert(param.to_id(), Expr::undefined(param.span()));
250 }
251 }
252
253 Pat::Rest(rest_pat) => {
254 if let Pat::Ident(param_id) = &*rest_pat.arg {
255 if let Some(usage) = self.data.vars.get(¶m_id.to_id()) {
256 if usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
257 || usage.ref_count != 1
258 || !usage.flags.contains(VarUsageInfoFlags::HAS_PROPERTY_ACCESS)
259 {
260 continue;
261 }
262
263 if e.args.iter().skip(idx).any(|arg| {
264 if arg.spread.is_some() {
265 return true;
266 }
267
268 match &*arg.expr {
269 Expr::Lit(Lit::Str(s)) if s.value.len() > 3 => true,
270 Expr::Lit(..) => false,
271 _ => true,
272 }
273 }) {
274 continue;
275 }
276
277 vars.insert(
278 param_id.to_id(),
279 ArrayLit {
280 span: param_id.span,
281 elems: e
282 .args
283 .iter()
284 .skip(idx)
285 .map(|arg| Some(arg.clone()))
286 .collect(),
287 }
288 .into(),
289 );
290 param.take();
291 }
292 }
293 }
294 _ => (),
295 }
296 }
297
298 if vars.is_empty() {
299 log_abort!("vars is empty");
300 return;
301 }
302
303 let ctx = self
304 .ctx
305 .clone()
306 .with(BitCtx::InFnLike, true)
307 .with(BitCtx::TopLevel, false);
308 let mut optimizer = self.with_ctx(ctx);
309 match find_body(callee) {
310 Some(Either::Left(body)) => {
311 trace_op!("inline: Inlining arguments");
312 optimizer.inline_vars_in_node(body, vars);
313 }
314 Some(Either::Right(body)) => {
315 trace_op!("inline: Inlining arguments");
316 optimizer.inline_vars_in_node(body, vars);
317 }
318 _ => {
319 unreachable!("find_body and find_params should match")
320 }
321 }
322
323 clean_params(callee);
324 }
325 }
326
327 pub(super) fn ignore_unused_args_of_iife(&mut self, e: &mut CallExpr) {
330 if !self.options.unused && !self.options.reduce_vars {
331 return;
332 }
333
334 let callee = match &mut e.callee {
335 Callee::Super(_) | Callee::Import(_) => return,
336 Callee::Expr(e) => &mut **e,
337 #[cfg(swc_ast_unknown)]
338 _ => panic!("unable to access unknown nodes"),
339 };
340
341 match find_body(callee) {
342 Some(body) => match body {
343 Either::Left(body) => {
344 if contains_arguments(body) {
345 return;
346 }
347 }
348 Either::Right(body) => {
349 if contains_arguments(body) {
350 return;
351 }
352 }
353 },
354 None => return,
355 }
356
357 if let Expr::Fn(FnExpr {
358 ident: Some(ident), ..
359 }) = callee
360 {
361 if self
362 .data
363 .vars
364 .get(&ident.to_id())
365 .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY))
366 .is_some()
367 {
368 return;
369 }
370 }
371
372 let mut removed = Vec::new();
373 let params = find_params(callee);
374 if let Some(mut params) = params {
375 for (idx, param) in params.iter_mut().enumerate() {
377 if let Pat::Ident(param) = &mut **param {
378 if let Some(usage) = self.data.vars.get(¶m.to_id()) {
379 if usage.ref_count == 0 {
380 removed.push(idx);
381 }
382 }
383 }
384 }
385
386 if removed.is_empty() {
387 log_abort!("`removed` is empty");
388 return;
389 }
390 } else {
391 unreachable!("find_body and find_params should match")
392 }
393
394 for idx in removed {
395 if let Some(arg) = e.args.get_mut(idx) {
396 if arg.spread.is_some() {
397 break;
398 }
399
400 let new = self.ignore_return_value(&mut arg.expr);
402
403 if let Some(new) = new {
404 arg.expr = Box::new(new);
405 } else {
406 arg.expr = Number {
408 span: arg.expr.span(),
409 value: 0.0,
410 raw: None,
411 }
412 .into();
413 }
414 } else {
415 break;
416 }
417 }
418 }
419
420 #[cfg_attr(feature = "debug", tracing::instrument(level = "debug", skip_all))]
421 pub(super) fn inline_vars_in_node<N>(&mut self, n: &mut N, mut vars: FxHashMap<Id, Box<Expr>>)
422 where
423 N: for<'aa> VisitMutWith<NormalMultiReplacer<'aa>>,
424 {
425 trace_op!("inline: inline_vars_in_node");
426
427 let mut v = NormalMultiReplacer::new(&mut vars, false);
428 n.visit_mut_with(&mut v);
429 self.changed |= v.changed;
430 }
431
432 fn may_invoke_iife(&self, call: &mut CallExpr) -> bool {
433 if self.options.inline == 0
434 && !(self.options.reduce_vars && self.options.reduce_fns && self.options.evaluate)
435 {
436 if !call.callee.span().is_dummy() {
437 log_abort!("skip");
438 return false;
439 }
440 }
441
442 trace_op!("iife: Checking noinline");
443
444 if self.has_noinline(call.ctxt) {
445 log_abort!("iife: Has no inline mark");
446 return false;
447 }
448
449 let callee = match &call.callee {
450 Callee::Super(_) | Callee::Import(_) => return false,
451 Callee::Expr(e) => &**e,
452 #[cfg(swc_ast_unknown)]
453 _ => panic!("unable to access unknown nodes"),
454 };
455
456 if self.ctx.bit_ctx.contains(BitCtx::DontInvokeIife) {
457 log_abort!("iife: Inline is prevented");
458 return false;
459 }
460
461 for arg in &call.args {
462 if arg.spread.is_some() {
463 log_abort!("iife: Found spread argument");
464 return false;
465 }
466 }
467
468 trace_op!("iife: Checking callee");
469
470 match callee {
471 Expr::Arrow(f) => {
472 if f.is_async {
473 log_abort!("iife: Cannot inline async fn");
474 return false;
475 }
476
477 if f.is_generator {
478 log_abort!("iife: Cannot inline generator");
479 return false;
480 }
481
482 if self.ctx.bit_ctx.contains(BitCtx::InParam) && !f.params.is_empty() {
483 log_abort!("iife: We don't invoke IIFE with params in function params");
484 return false;
485 }
486
487 if f.params.iter().any(|param| !param.is_ident()) {
488 return false;
489 }
490 }
491
492 Expr::Fn(f) => {
493 if f.function.is_async {
494 log_abort!("iife: [x] Cannot inline async fn");
495 return false;
496 }
497
498 if f.function.is_generator {
499 log_abort!("iife: [x] Cannot inline generator");
500 return false;
501 }
502
503 if self.ctx.bit_ctx.contains(BitCtx::InParam) && !f.function.params.is_empty() {
504 log_abort!("iife: We don't invoke IIFE with params in function params");
505 return false;
506 }
507
508 if f.function.params.iter().any(|param| !param.pat.is_ident()) {
510 return false;
511 }
512
513 trace_op!("iife: Checking recursiveness");
514
515 if let Some(i) = &f.ident {
516 if self
517 .data
518 .vars
519 .get(&i.to_id())
520 .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY))
521 .is_some()
522 {
523 log_abort!("iife: [x] Recursive?");
524 return false;
525 }
526 }
527
528 let body = f.function.body.as_ref().unwrap();
529 if contains_this_expr(body) || contains_arguments(body) {
530 return false;
531 }
532 }
533
534 _ => return false,
535 };
536
537 true
538 }
539
540 pub(super) fn invoke_iife(&mut self, e: &mut Expr) {
559 trace_op!("iife: invoke_iife");
560
561 let call = match e {
562 Expr::Call(v) => v,
563 _ => return,
564 };
565
566 if !self.may_invoke_iife(call) {
567 return;
568 }
569
570 let callee = match &mut call.callee {
571 Callee::Super(_) | Callee::Import(_) => return,
572 Callee::Expr(e) => &mut **e,
573 #[cfg(swc_ast_unknown)]
574 _ => panic!("unable to access unknown nodes"),
575 };
576
577 match callee {
578 Expr::Arrow(f) => {
579 let param_ids = f.params.iter().map(|p| &p.as_ident().unwrap().id);
580
581 match &mut *f.body {
582 BlockStmtOrExpr::BlockStmt(body) => {
583 let new =
584 self.inline_fn_like(param_ids, f.params.len(), body, &mut call.args);
585 if let Some(new) = new {
586 self.changed = true;
587 report_change!("inline: Inlining a function call (arrow)");
588
589 *e = new;
590 }
591 }
592 BlockStmtOrExpr::Expr(body) => {
593 if !self.can_extract_param(param_ids.clone()) {
594 return;
595 }
596
597 if let Expr::Lit(Lit::Num(..)) = &**body {
598 if self.ctx.bit_ctx.contains(BitCtx::InObjOfNonComputedMember) {
599 return;
600 }
601 }
602
603 self.changed = true;
604 report_change!("inline: Inlining a function call (arrow)");
605
606 let mut exprs = vec![Box::new(make_number(DUMMY_SP, 0.0))];
607
608 let vars = self.inline_fn_param(
609 param_ids,
610 f.params.len(),
611 &mut call.args,
612 &mut exprs,
613 );
614
615 if !vars.is_empty() {
616 self.prepend_stmts.push(
617 VarDecl {
618 span: DUMMY_SP,
619 kind: VarDeclKind::Let,
620 declare: Default::default(),
621 decls: vars,
622 ..Default::default()
623 }
624 .into(),
625 )
626 }
627
628 if call.args.len() > f.params.len() {
629 for arg in &mut call.args[f.params.len()..] {
630 exprs.push(arg.expr.take());
631 }
632 }
633 if self.vars.inline_with_multi_replacer(body) {
634 self.changed = true;
635 }
636 exprs.push(body.take());
637
638 report_change!("inline: Inlining a call to an arrow function");
639 *e = *Expr::from_exprs(exprs);
640 e.visit_mut_with(self);
641 }
642 #[cfg(swc_ast_unknown)]
643 _ => panic!("unable to access unknown nodes"),
644 }
645 }
646 Expr::Fn(f) => {
647 trace_op!("iife: Empty function");
648
649 let body = f.function.body.as_mut().unwrap();
650 if body.stmts.is_empty() && call.args.is_empty() {
651 self.changed = true;
652 report_change!("iife: Inlining an empty function call as `undefined`");
653 *e = *Expr::undefined(f.function.span);
654 return;
655 }
656
657 let param_ids = f
658 .function
659 .params
660 .iter()
661 .map(|p| &p.pat.as_ident().unwrap().id);
662
663 #[cfg(feature = "debug")]
664 let param_ids_for_debug = param_ids.clone();
665
666 let new =
667 self.inline_fn_like(param_ids, f.function.params.len(), body, &mut call.args);
668 if let Some(new) = new {
669 self.changed = true;
670 report_change!(
671 "inline: Inlining a function call (params = {:?})",
672 param_ids_for_debug
673 );
674
675 dump_change_detail!("{}", dump(&new, false));
676
677 *e = new;
678 }
679
680 }
682 _ => {}
683 }
684 }
685
686 pub(super) fn invoke_iife_stmt(&mut self, e: &mut Expr, is_return: bool) -> Option<BlockStmt> {
687 trace_op!("iife: invoke_iife");
688
689 let call = match e {
690 Expr::Call(v) => v,
691 Expr::Unary(UnaryExpr { arg, .. }) if !is_return => {
692 if let Expr::Call(v) = &mut **arg {
693 v
694 } else {
695 return None;
696 }
697 }
698 _ => return None,
699 };
700
701 if !self.may_invoke_iife(call) {
702 return None;
703 }
704
705 let callee = match &mut call.callee {
706 Callee::Super(_) | Callee::Import(_) => return None,
707 Callee::Expr(e) => &mut **e,
708 #[cfg(swc_ast_unknown)]
709 _ => panic!("unable to access unknown nodes"),
710 };
711
712 match callee {
713 Expr::Arrow(f) => {
714 match &mut *f.body {
715 BlockStmtOrExpr::Expr(_) => None,
717 BlockStmtOrExpr::BlockStmt(block_stmt) => {
718 let param_ids = f.params.iter().map(|p| &p.as_ident().unwrap().id);
719 self.inline_fn_like_stmt(
720 param_ids,
721 f.params.len(),
722 block_stmt,
723 &mut call.args,
724 is_return,
725 call.span,
726 )
727 }
728 #[cfg(swc_ast_unknown)]
729 _ => panic!("unable to access unknown nodes"),
730 }
731 }
732 Expr::Fn(f) => {
733 let body = f.function.body.as_mut().unwrap();
734 let param_ids = f
735 .function
736 .params
737 .iter()
738 .map(|p| &p.pat.as_ident().unwrap().id);
739 self.inline_fn_like_stmt(
740 param_ids,
741 f.function.params.len(),
742 body,
743 &mut call.args,
744 is_return,
745 call.span,
746 )
747 }
748 _ => None,
749 }
750 }
751
752 fn is_return_arg_simple_enough_for_iife_eval(&self, e: &Expr) -> bool {
753 match e {
754 Expr::Lit(..) | Expr::Ident(..) => true,
755
756 Expr::Bin(BinExpr { op, .. }) if op.may_short_circuit() => false,
757
758 Expr::Bin(e) => {
759 self.is_return_arg_simple_enough_for_iife_eval(&e.left)
760 && self.is_return_arg_simple_enough_for_iife_eval(&e.right)
761 }
762
763 Expr::Cond(e) => {
764 self.is_return_arg_simple_enough_for_iife_eval(&e.test)
765 && self.is_return_arg_simple_enough_for_iife_eval(&e.cons)
766 && self.is_return_arg_simple_enough_for_iife_eval(&e.alt)
767 }
768
769 _ => false,
770 }
771 }
772
773 fn can_extract_param<'a>(&self, param_ids: impl Iterator<Item = &'a Ident> + Clone) -> bool {
774 if !self.may_add_ident() {
776 for pid in param_ids.clone() {
777 if let Some(usage) = self.data.vars.get(&pid.to_id()) {
778 if usage.ref_count > 1
779 || usage.assign_count > 0
780 || usage.flags.contains(VarUsageInfoFlags::INLINE_PREVENTED)
781 {
782 log_abort!("iife: [x] Cannot inline because of usage of `{}`", pid);
783 return false;
784 }
785 }
786 }
787 }
788
789 for pid in param_ids {
790 if self.ident_reserved(&pid.sym) {
791 log_abort!(
792 "iife: [x] Cannot inline because of reservation of `{}`",
793 pid
794 );
795 return false;
796 }
797 }
798
799 true
800 }
801
802 fn can_inline_fn_stmt(&self, stmt: &Stmt, for_stmt: bool) -> bool {
803 match stmt {
804 Stmt::Decl(Decl::Var(var)) => {
805 for decl in &var.decls {
806 for id in find_pat_ids::<_, Id>(&decl.name) {
807 if self.ident_reserved(&id.0) {
808 log_abort!("iife: [x] Cannot inline because reservation of `{}`", id.0);
809 return false;
810 }
811 }
812 }
813
814 if matches!(
815 &**var,
816 VarDecl {
817 kind: VarDeclKind::Var | VarDeclKind::Let,
818 ..
819 }
820 ) {
821 for decl in &var.decls {
822 match &decl.name {
823 Pat::Ident(id) if id.sym == "arguments" => return false,
824 Pat::Ident(id) => {
825 if self.vars.has_pending_inline_for(&id.to_id()) {
826 log_abort!(
827 "iife: [x] Cannot inline because pending inline of `{}`",
828 id.id
829 );
830 return false;
831 }
832 }
833
834 _ => return false,
835 }
836 }
837
838 if self.ctx.bit_ctx.contains(BitCtx::ExecutedMultipleTime) {
839 return false;
840 }
841
842 true
843 } else {
844 for_stmt
845 }
846 }
847
848 Stmt::Expr(_) => true,
849
850 Stmt::Return(ReturnStmt { arg, .. }) => match arg.as_deref() {
851 Some(Expr::Lit(Lit::Num(..))) => {
852 for_stmt || !self.ctx.bit_ctx.contains(BitCtx::InObjOfNonComputedMember)
853 }
854 _ => true,
855 },
856 Stmt::Block(b) => b
857 .stmts
858 .iter()
859 .all(|stmt| self.can_inline_fn_stmt(stmt, for_stmt)),
860 Stmt::If(i) if for_stmt => {
861 self.can_inline_fn_stmt(&i.cons, for_stmt)
862 && i.alt
863 .as_ref()
864 .map(|a| self.can_inline_fn_stmt(a, for_stmt))
865 .unwrap_or(true)
866 }
867 Stmt::Switch(s) if for_stmt => s
868 .cases
869 .iter()
870 .flat_map(|case| case.cons.iter())
871 .all(|stmt| self.can_inline_fn_stmt(stmt, for_stmt)),
872 _ => for_stmt,
873 }
874 }
875
876 fn can_inline_fn_like<'a>(
877 &self,
878 param_ids: impl Iterator<Item = &'a Ident> + Clone,
879 params_len: usize,
880 body: &BlockStmt,
881 for_stmt: bool,
882 ) -> bool {
883 trace_op!("can_inline_fn_like");
884
885 if body.stmts.len() == 1 {
886 if let Stmt::Return(ReturnStmt { arg: Some(arg), .. }) = &body.stmts[0] {
887 if self.is_return_arg_simple_enough_for_iife_eval(arg) {
888 return true;
889 }
890 }
891 }
892
893 if !self.can_extract_param(param_ids.clone()) {
894 return false;
895 }
896
897 if self.data.top.contains(ScopeData::HAS_EVAL_CALL) {
904 log_abort!("iife: [x] Aborting because of eval");
905 return false;
906 }
907
908 if !self.may_add_ident() {
909 let has_decl = if for_stmt {
910 false
912 } else {
913 body.stmts.iter().any(|stmt| matches!(stmt, Stmt::Decl(..)))
914 };
915 if has_decl {
916 log_abort!("iife: [x] Found decl");
917 return false;
918 }
919 }
920
921 if self.ctx.bit_ctx.contains(BitCtx::ExecutedMultipleTime) {
922 if params_len != 0 {
923 let captured = idents_captured_by(body);
924
925 for param in param_ids {
926 if captured.contains(¶m.to_id()) {
927 log_abort!(
928 "iife: [x] Cannot inline because of the capture of `{}`",
929 param
930 );
931 return false;
932 }
933 }
934 }
935 }
936
937 if let Some(stmt) = body.stmts.first() {
938 if stmt.is_use_strict() {
939 return false;
940 }
941 }
942
943 if !body
944 .stmts
945 .iter()
946 .all(|stmt| self.can_inline_fn_stmt(stmt, for_stmt))
947 {
948 return false;
949 }
950
951 true
952 }
953
954 fn inline_fn_param<'a>(
955 &mut self,
956 params: impl Iterator<Item = &'a Ident>,
957 params_len: usize,
958 args: &mut [ExprOrSpread],
959 exprs: &mut Vec<Box<Expr>>,
960 ) -> Vec<VarDeclarator> {
961 let mut vars = Vec::with_capacity(params_len);
962
963 for (idx, param) in params.enumerate() {
964 let arg = args.get_mut(idx).map(|arg| arg.expr.take());
965
966 let no_arg = arg.is_none();
967
968 if let Some(arg) = arg {
969 if let Some(usage) = self.data.vars.get_mut(¶m.to_id()) {
970 if usage.ref_count == 1
971 && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
972 && usage.property_mutation_count == 0
973 && matches!(
974 &*arg,
975 Expr::Lit(
976 Lit::Num(..) | Lit::Str(..) | Lit::Bool(..) | Lit::BigInt(..)
977 )
978 )
979 {
980 self.vars.vars_for_inlining.insert(param.to_id(), arg);
982 continue;
983 }
984
985 usage.ref_count += 1;
986 }
987
988 exprs.push(
989 AssignExpr {
990 span: DUMMY_SP,
991 op: op!("="),
992 left: (*param).clone().into(),
993 right: arg,
994 }
995 .into(),
996 )
997 };
998
999 vars.push(VarDeclarator {
1000 span: DUMMY_SP,
1001 name: (*param).clone().into(),
1002 init: if self.ctx.bit_ctx.contains(BitCtx::ExecutedMultipleTime) && no_arg {
1003 Some(Expr::undefined(DUMMY_SP))
1004 } else {
1005 None
1006 },
1007 definite: Default::default(),
1008 });
1009 }
1010
1011 vars
1012 }
1013
1014 fn inline_fn_param_stmt<'a>(
1015 &mut self,
1016 params: impl Iterator<Item = &'a Ident>,
1017 params_len: usize,
1018 args: &mut [ExprOrSpread],
1019 ) -> Vec<VarDeclarator> {
1020 let mut vars = Vec::with_capacity(params_len);
1021
1022 for (idx, param) in params.enumerate() {
1023 let mut arg = args.get_mut(idx).map(|arg| arg.expr.take());
1024
1025 if let Some(arg) = &mut arg {
1026 if let Some(usage) = self.data.vars.get_mut(¶m.to_id()) {
1027 if usage.ref_count == 1
1028 && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
1029 && usage.property_mutation_count == 0
1030 && matches!(
1031 &**arg,
1032 Expr::Lit(
1033 Lit::Num(..) | Lit::Str(..) | Lit::Bool(..) | Lit::BigInt(..)
1034 )
1035 )
1036 {
1037 self.vars
1039 .vars_for_inlining
1040 .insert(param.to_id(), arg.take());
1041 continue;
1042 }
1043
1044 usage.ref_count += 1;
1045 }
1046 };
1047
1048 vars.push(VarDeclarator {
1049 span: DUMMY_SP,
1050 name: param.clone().into(),
1051 init: if self.ctx.bit_ctx.contains(BitCtx::ExecutedMultipleTime) && arg.is_none() {
1052 Some(Expr::undefined(DUMMY_SP))
1053 } else {
1054 arg
1055 },
1056 definite: Default::default(),
1057 });
1058 }
1059
1060 vars
1061 }
1062
1063 fn inline_into_expr(&mut self, stmts: Vec<Stmt>, exprs: &mut Vec<Box<Expr>>) -> Option<Expr> {
1064 for mut stmt in stmts {
1065 match stmt {
1066 Stmt::Decl(Decl::Var(ref mut var)) => {
1067 for decl in &mut var.decls {
1068 if decl.init.is_some() {
1069 let ids = find_pat_ids(decl);
1070
1071 for id in ids {
1072 if let Some(usage) = self.data.vars.get_mut(&id) {
1073 usage.ref_count += 1;
1076 }
1077 }
1078
1079 exprs.push(
1080 AssignExpr {
1081 span: DUMMY_SP,
1082 op: op!("="),
1083 left: decl.name.clone().try_into().unwrap(),
1084 right: decl.init.take().unwrap(),
1085 }
1086 .into(),
1087 )
1088 }
1089 }
1090
1091 self.prepend_stmts.push(stmt);
1092 }
1093
1094 Stmt::Expr(stmt) => {
1095 exprs.push(stmt.expr);
1096 }
1097
1098 Stmt::Block(stmt) => {
1099 if let Some(e) = self.inline_into_expr(stmt.stmts, exprs) {
1100 return Some(e);
1101 }
1102 }
1103
1104 Stmt::Return(stmt) => {
1105 let span = stmt.span;
1106 let val = *stmt.arg.unwrap_or_else(|| Expr::undefined(span));
1107 exprs.push(Box::new(val));
1108
1109 let mut e = SeqExpr {
1110 span: DUMMY_SP,
1111 exprs: exprs.take(),
1112 };
1113 self.merge_sequences_in_seq_expr(&mut e);
1114
1115 let mut e = e.into();
1116 self.normalize_expr(&mut e);
1117 return Some(e);
1118 }
1119 _ => {}
1120 }
1121 }
1122
1123 None
1124 }
1125
1126 fn inline_fn_like<'a>(
1127 &mut self,
1128 params: impl Iterator<Item = &'a Ident> + Clone,
1129 params_len: usize,
1130 body: &mut BlockStmt,
1131 args: &mut [ExprOrSpread],
1132 ) -> Option<Expr> {
1133 if !self.can_inline_fn_like(params.clone(), params_len, &*body, false) {
1134 return None;
1135 }
1136
1137 if self.vars.inline_with_multi_replacer(body) {
1138 self.changed = true;
1139 }
1140
1141 let mut exprs = Vec::new();
1142 let vars = self.inline_fn_param(params, params_len, args, &mut exprs);
1143
1144 if args.len() > params_len {
1145 for arg in &mut args[params_len..] {
1146 exprs.push(arg.expr.take());
1147 }
1148 }
1149
1150 if !vars.is_empty() {
1151 trace_op!("iife: Creating variables: {:?}", vars);
1152
1153 self.prepend_stmts.push(
1154 VarDecl {
1155 span: DUMMY_SP,
1156 kind: VarDeclKind::Var,
1157 declare: Default::default(),
1158 decls: vars,
1159 ..Default::default()
1160 }
1161 .into(),
1162 );
1163 }
1164
1165 if let Some(e) = self.inline_into_expr(body.stmts.take(), &mut exprs) {
1166 return Some(e);
1167 }
1168
1169 if let Some(last) = exprs.last_mut() {
1170 *last = UnaryExpr {
1171 span: DUMMY_SP,
1172 op: op!("void"),
1173 arg: last.take(),
1174 }
1175 .into();
1176 } else {
1177 return Some(*Expr::undefined(body.span));
1178 }
1179
1180 let mut e = SeqExpr {
1181 span: DUMMY_SP,
1182 exprs,
1183 };
1184 self.merge_sequences_in_seq_expr(&mut e);
1185
1186 let mut e = e.into();
1187 self.normalize_expr(&mut e);
1188 Some(e)
1189 }
1190
1191 fn inline_fn_like_stmt<'a>(
1192 &mut self,
1193 params: impl Iterator<Item = &'a Ident> + Clone + std::fmt::Debug,
1194 params_len: usize,
1195 body: &mut BlockStmt,
1196 args: &mut [ExprOrSpread],
1197 is_return: bool,
1198 span: Span,
1199 ) -> Option<BlockStmt> {
1200 if !self.can_inline_fn_like(params.clone(), params_len, body, true) {
1201 return None;
1202 }
1203
1204 let mut decl = DeclVisitor { count: 0 };
1205
1206 body.visit_with(&mut decl);
1207
1208 if !self.may_add_ident() && decl.count > 0 {
1209 return None;
1210 }
1211
1212 if decl.count
1213 + (params_len.saturating_sub(
1214 args.iter()
1215 .filter(|a| {
1216 a.expr.is_ident() || a.expr.as_lit().map(|l| !l.is_regex()).unwrap_or(false)
1217 })
1218 .count(),
1219 )) * 2
1220 > 4
1221 {
1222 return None;
1223 }
1224
1225 let mut has_return = ReturnVisitor { found: false };
1226
1227 if !is_return {
1228 body.visit_with(&mut has_return);
1229
1230 if has_return.found {
1231 return None;
1232 }
1233 }
1234
1235 self.changed = true;
1236 report_change!("inline: Inlining a function call (params = {params:?})");
1237
1238 let mut stmts = Vec::with_capacity(body.stmts.len() + 2);
1239
1240 let param_decl = self.inline_fn_param_stmt(params, params_len, args);
1241
1242 if !param_decl.is_empty() {
1243 let param_decl = Stmt::Decl(Decl::Var(Box::new(VarDecl {
1244 span: DUMMY_SP,
1245 ctxt: SyntaxContext::empty(),
1246 kind: VarDeclKind::Var,
1247 declare: false,
1248 decls: param_decl,
1249 })));
1250
1251 stmts.push(param_decl);
1252 }
1253
1254 if args.len() > params_len {
1255 let mut exprs = Vec::new();
1256 for arg in args[params_len..].iter_mut() {
1257 exprs.push(arg.expr.take())
1258 }
1259
1260 let expr = Stmt::Expr(ExprStmt {
1261 span: DUMMY_SP,
1262 expr: Box::new(Expr::Seq(SeqExpr {
1263 span: DUMMY_SP,
1264 exprs,
1265 })),
1266 });
1267
1268 stmts.push(expr);
1269 }
1270
1271 stmts.extend(body.stmts.take());
1272
1273 Some(BlockStmt {
1274 span,
1275 ctxt: SyntaxContext::empty().apply_mark(self.marks.fake_block),
1276 stmts,
1277 })
1278 }
1279
1280 fn can_be_inlined_for_iife(&self, arg: &Expr) -> bool {
1281 match arg {
1282 Expr::Lit(..) => true,
1283
1284 Expr::Unary(UnaryExpr {
1285 op: op!("void"),
1286 arg,
1287 ..
1288 })
1289 | Expr::Unary(UnaryExpr {
1290 op: op!("!"), arg, ..
1291 }) => self.can_be_inlined_for_iife(arg),
1292
1293 Expr::Ident(..) => true,
1294
1295 Expr::Member(MemberExpr { obj, prop, .. }) if !prop.is_computed() => {
1296 self.can_be_inlined_for_iife(obj)
1297 }
1298
1299 Expr::Bin(BinExpr {
1300 op, left, right, ..
1301 }) => match op {
1302 op!(bin, "+") | op!("*") => {
1303 self.can_be_inlined_for_iife(left) && self.can_be_inlined_for_iife(right)
1304 }
1305 _ => false,
1306 },
1307
1308 Expr::Object(ObjectLit { props, .. }) => {
1309 for prop in props {
1310 match prop {
1311 PropOrSpread::Spread(_) => return false,
1312 PropOrSpread::Prop(p) => match &**p {
1313 Prop::Shorthand(_) => {}
1314 Prop::KeyValue(kv) => {
1315 if let PropName::Computed(key) = &kv.key {
1316 if !self.can_be_inlined_for_iife(&key.expr) {
1317 return false;
1318 }
1319 }
1320
1321 if !self.can_be_inlined_for_iife(&kv.value) {
1322 return false;
1323 }
1324 }
1325 Prop::Assign(p) => {
1326 if !self.can_be_inlined_for_iife(&p.value) {
1327 return false;
1328 }
1329 }
1330 _ => return false,
1331 },
1332 #[cfg(swc_ast_unknown)]
1333 _ => panic!("unable to access unknown nodes"),
1334 }
1335 }
1336
1337 true
1338 }
1339
1340 Expr::Arrow(ArrowExpr {
1341 params,
1342 body,
1343 is_async: false,
1344 is_generator: false,
1345 ..
1346 }) if body.is_expr() => {
1347 params.iter().all(|p| p.is_ident())
1348 && self.can_be_inlined_for_iife(body.as_expr().unwrap())
1349 }
1350
1351 _ => false,
1352 }
1353 }
1354}
1355
1356fn find_scope<'a>(data: &'a ProgramData, callee: &Expr) -> Option<&'a ScopeData> {
1357 match callee {
1358 Expr::Arrow(callee) => data.scopes.get(&callee.ctxt),
1359 Expr::Fn(callee) => data.scopes.get(&callee.function.ctxt),
1360 _ => None,
1361 }
1362}
1363
1364fn find_params(callee: &mut Expr) -> Option<Vec<&mut Pat>> {
1365 match callee {
1366 Expr::Arrow(callee) => Some(callee.params.iter_mut().collect()),
1367 Expr::Fn(callee) => Some(
1368 callee
1369 .function
1370 .params
1371 .iter_mut()
1372 .map(|param| &mut param.pat)
1373 .collect(),
1374 ),
1375 _ => None,
1376 }
1377}
1378fn find_body(callee: &mut Expr) -> Option<Either<&mut BlockStmt, &mut Expr>> {
1379 match callee {
1380 Expr::Arrow(e) => match &mut *e.body {
1381 BlockStmtOrExpr::BlockStmt(b) => Some(Either::Left(b)),
1382 BlockStmtOrExpr::Expr(b) => Some(Either::Right(&mut **b)),
1383 #[cfg(swc_ast_unknown)]
1384 _ => panic!("unable to access unknown nodes"),
1385 },
1386 Expr::Fn(e) => Some(Either::Left(e.function.body.as_mut().unwrap())),
1387 _ => None,
1388 }
1389}
1390
1391pub struct ReturnVisitor {
1392 found: bool,
1393}
1394
1395impl Visit for ReturnVisitor {
1396 noop_visit_type!(fail);
1397
1398 fn visit_constructor(&mut self, _: &Constructor) {}
1400
1401 fn visit_fn_decl(&mut self, _: &FnDecl) {}
1403
1404 fn visit_fn_expr(&mut self, _: &FnExpr) {}
1406
1407 fn visit_function(&mut self, _: &Function) {}
1409
1410 fn visit_getter_prop(&mut self, n: &GetterProp) {
1412 n.key.visit_with(self);
1413 }
1414
1415 fn visit_method_prop(&mut self, n: &MethodProp) {
1417 n.key.visit_with(self);
1418 n.function.visit_with(self);
1419 }
1420
1421 fn visit_setter_prop(&mut self, n: &SetterProp) {
1423 n.key.visit_with(self);
1424 n.param.visit_with(self);
1425 }
1426
1427 fn visit_expr(&mut self, _: &Expr) {}
1428
1429 fn visit_return_stmt(&mut self, _: &ReturnStmt) {
1430 self.found = true;
1431 }
1432}
1433
1434pub struct DeclVisitor {
1435 count: usize,
1436}
1437
1438impl Visit for DeclVisitor {
1439 noop_visit_type!(fail);
1440
1441 fn visit_constructor(&mut self, _: &Constructor) {}
1443
1444 fn visit_fn_decl(&mut self, _: &FnDecl) {}
1446
1447 fn visit_fn_expr(&mut self, _: &FnExpr) {}
1449
1450 fn visit_function(&mut self, _: &Function) {}
1452
1453 fn visit_getter_prop(&mut self, n: &GetterProp) {
1455 n.key.visit_with(self);
1456 }
1457
1458 fn visit_method_prop(&mut self, n: &MethodProp) {
1460 n.key.visit_with(self);
1461 n.function.visit_with(self);
1462 }
1463
1464 fn visit_setter_prop(&mut self, n: &SetterProp) {
1466 n.key.visit_with(self);
1467 n.param.visit_with(self);
1468 }
1469
1470 fn visit_expr(&mut self, _: &Expr) {}
1471
1472 fn visit_decl(&mut self, d: &Decl) {
1473 self.count += match d {
1474 Decl::Class(_) | Decl::Fn(_) => 1,
1475 Decl::Var(var_decl) => var_decl.decls.len(),
1476 Decl::Using(using_decl) => using_decl.decls.len(),
1477 Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) | Decl::TsModule(_) => 0,
1478 #[cfg(swc_ast_unknown)]
1479 _ => panic!("unable to access unknown nodes"),
1480 };
1481 }
1482
1483 fn visit_var_decl_or_expr(&mut self, node: &VarDeclOrExpr) {
1484 if let VarDeclOrExpr::VarDecl(v) = node {
1485 self.count += v.decls.len()
1486 }
1487 }
1488}
1489
1490impl Optimizer<'_> {
1492 #[cfg_attr(feature = "debug", tracing::instrument(level = "debug", skip_all))]
1496 pub(super) fn invoke_iife_in_seq_expr(&mut self, seq: &mut SeqExpr) {
1497 trace_op!("iife: invoke_iife_in_seq_expr");
1498
1499 for expr in &mut seq.exprs {
1501 self.invoke_iife(expr);
1503 }
1504
1505 self.optimize_arrow_iife_patterns_in_seq(seq);
1508 }
1509
1510 fn optimize_arrow_iife_patterns_in_seq(&mut self, seq: &mut SeqExpr) {
1513 let mut changed = false;
1514
1515 for i in 0..seq.exprs.len() {
1516 let can_optimize = if let Expr::Call(call) = &*seq.exprs[i] {
1518 if let Callee::Expr(callee) = &call.callee {
1519 if let Expr::Arrow(arrow) = &**callee {
1520 if let BlockStmtOrExpr::Expr(_body) = &*arrow.body {
1523 self.can_optimize_arrow_iife_in_seq(arrow, call)
1524 } else {
1525 false
1526 }
1527 } else {
1528 false
1529 }
1530 } else {
1531 false
1532 }
1533 } else {
1534 false
1535 };
1536
1537 if can_optimize {
1539 let expr = &mut seq.exprs[i];
1542 if let Expr::Call(call) = &mut **expr {
1543 if let Callee::Expr(callee) = &call.callee {
1544 if let Expr::Arrow(arrow) = &**callee {
1545 if let BlockStmtOrExpr::Expr(_body) = &*arrow.body {
1546 let new_expr = self.optimize_single_arrow_iife_in_seq(arrow, call);
1547
1548 if let Some(new_expr) = new_expr {
1549 **expr = new_expr;
1550 changed = true;
1551 }
1552 }
1553 }
1554 }
1555 }
1556 }
1557 }
1558
1559 if changed {
1560 self.changed = true;
1561 report_change!(
1562 "iife: Enhanced optimization of arrow functions in sequence expressions"
1563 );
1564 }
1565 }
1566
1567 fn can_optimize_arrow_iife_in_seq(&self, arrow: &ArrowExpr, call: &CallExpr) -> bool {
1569 if arrow.is_async || arrow.is_generator {
1571 return false;
1572 }
1573
1574 if !arrow.params.iter().all(|p| p.is_ident()) {
1576 return false;
1577 }
1578
1579 if call.args.iter().any(|arg| arg.spread.is_some()) {
1581 return false;
1582 }
1583
1584 if let BlockStmtOrExpr::Expr(body) = &*arrow.body {
1586 self.is_simple_expr_for_seq_optimization(body)
1587 } else {
1588 false
1589 }
1590 }
1591
1592 fn is_simple_expr_for_seq_optimization(&self, expr: &Expr) -> bool {
1594 match expr {
1595 Expr::Lit(..) | Expr::Ident(..) => true,
1596 Expr::Bin(bin) if !bin.op.may_short_circuit() => {
1597 self.is_simple_expr_for_seq_optimization(&bin.left)
1598 && self.is_simple_expr_for_seq_optimization(&bin.right)
1599 }
1600 Expr::Unary(unary) => self.is_simple_expr_for_seq_optimization(&unary.arg),
1601 Expr::Member(member) if !member.prop.is_computed() => {
1602 self.is_simple_expr_for_seq_optimization(&member.obj)
1603 }
1604 _ => false,
1605 }
1606 }
1607
1608 fn optimize_single_arrow_iife_in_seq(
1610 &mut self,
1611 arrow: &ArrowExpr,
1612 call: &CallExpr,
1613 ) -> Option<Expr> {
1614 if let BlockStmtOrExpr::Expr(body) = &*arrow.body {
1615 if arrow.params.is_empty() && call.args.is_empty() {
1618 return Some((**body).clone());
1619 }
1620
1621 if arrow.params.len() == call.args.len() {
1623 let can_inline = arrow.params.iter().zip(&call.args).all(|(param, arg)| {
1624 param.is_ident() && self.is_simple_expr_for_seq_optimization(&arg.expr)
1625 });
1626
1627 if can_inline {
1628 let mut substitutions = FxHashMap::default();
1630 for (param, arg) in arrow.params.iter().zip(&call.args) {
1631 if let Pat::Ident(ident) = param {
1632 substitutions.insert(ident.to_id(), arg.expr.clone());
1633 }
1634 }
1635
1636 let mut new_body = (**body).clone();
1638 if !substitutions.is_empty() {
1639 let mut replacer = NormalMultiReplacer::new(&mut substitutions, false);
1640 new_body.visit_mut_with(&mut replacer);
1641 }
1642
1643 return Some(new_body);
1644 }
1645 }
1646 }
1647
1648 None
1649 }
1650}