swc_ecma_minifier/compress/optimize/
inline.rs1use std::ops::Deref;
2
3use rustc_hash::{FxHashMap, FxHashSet};
4use swc_atoms::atom;
5use swc_common::{util::take::Take, EqIgnoreSpan, Mark};
6use swc_ecma_ast::*;
7use swc_ecma_usage_analyzer::alias::{collect_infects_from, AliasConfig};
8use swc_ecma_utils::{
9 class_has_side_effect, collect_decls, contains_this_expr, find_pat_ids, ExprExt, Remapper,
10};
11use swc_ecma_visit::VisitMutWith;
12
13use super::Optimizer;
14use crate::{
15 compress::{
16 optimize::{util::is_valid_for_lhs, BitCtx},
17 util::contains_super,
18 },
19 program_data::{ScopeData, VarUsageInfo, VarUsageInfoFlags},
20 util::{
21 idents_captured_by, idents_used_by, idents_used_by_ignoring_nested, size::SizeWithCtxt,
22 },
23};
24
25impl Optimizer<'_> {
27 pub(super) fn store_var_for_inlining(
32 &mut self,
33 ident: &mut Ident,
34 init: &mut Expr,
35 can_drop: bool,
36 ) {
37 trace_op!(
38 "inline: store_var_for_inlining({}, may_remove = {:?})",
39 crate::debug::dump(ident, false),
40 self.may_remove_ident(ident)
41 );
42
43 if self.data.top.contains(ScopeData::HAS_EVAL_CALL) {
44 return;
45 }
46
47 if ident.sym == "arguments" {
49 return;
50 }
51
52 if let Expr::Arrow(ArrowExpr { body, .. }) = init {
53 if contains_super(body) {
54 return;
55 }
56 if contains_this_expr(body) {
57 return;
58 }
59 }
60
61 if let Some(usage) = self.data.vars.get(&ident.to_id()) {
62 let ref_count = usage.ref_count - u32::from(can_drop && usage.ref_count > 1);
63 if !usage.flags.contains(VarUsageInfoFlags::VAR_INITIALIZED) {
64 return;
65 }
66
67 if self.data.top.contains(ScopeData::USED_ARGUMENTS)
68 && usage
69 .flags
70 .contains(VarUsageInfoFlags::DECLARED_AS_FN_PARAM)
71 {
72 return;
73 }
74 if usage
75 .flags
76 .contains(VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM)
77 {
78 return;
79 }
80 if usage.flags.contains(VarUsageInfoFlags::INLINE_PREVENTED) {
81 return;
82 }
83
84 let may_remove = self.may_remove_ident(ident);
85
86 if !may_remove && usage.var_kind != Some(VarDeclKind::Const) {
87 log_abort!(
88 "inline: [x] Preserving non-const variable `{}` because it's top-level",
89 crate::debug::dump(ident, false)
90 );
91 return;
92 }
93
94 if usage.flags.contains(VarUsageInfoFlags::USED_ABOVE_DECL) {
95 log_abort!("inline: [x] It's cond init or used before decl",);
96 return;
97 }
98
99 if ref_count == 0 {
101 self.mode.store(ident.to_id(), &*init);
102
103 if init.may_have_side_effects(self.ctx.expr_ctx) {
104 return;
106 }
107
108 return;
110 }
111
112 let is_inline_enabled =
113 self.options.reduce_vars || self.options.collapse_vars || self.options.inline != 0;
114
115 let mut inlined_into_init = false;
116
117 let id = ident.to_id();
118
119 if usage.flags.contains(VarUsageInfoFlags::DECLARED)
126 && !usage.mutated()
127 && usage.accessed_props.is_empty()
128 && !usage.flags.intersects(
129 VarUsageInfoFlags::INDEXED_WITH_DYNAMIC_KEY
130 .union(VarUsageInfoFlags::USED_AS_REF),
131 )
132 && !usage.is_infected()
133 && is_inline_enabled
134 {
135 if let Expr::Array(arr) = init {
136 if arr.elems.len() < 32
137 && arr.elems.iter().all(|e| match e {
138 Some(ExprOrSpread { spread: None, expr }) => match &**expr {
139 Expr::Lit(..) => true,
140 _ => false,
141 },
142 _ => false,
143 })
144 {
145 inlined_into_init = true;
146 self.vars.inline_with_multi_replacer(arr);
147 report_change!(
148 "inline: Decided to store '{}{:?}' for array access",
149 ident.sym,
150 ident.ctxt
151 );
152 self.vars
153 .lits_for_array_access
154 .insert(ident.to_id(), Box::new(init.clone()));
155 }
156 }
157 }
158
159 if !usage.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL) {
160 match init {
161 Expr::Lit(..) | Expr::Ident(..) | Expr::Arrow(..) => {}
162
163 Expr::Unary(UnaryExpr {
164 op: op!("!"), arg, ..
165 }) if matches!(&**arg, Expr::Lit(..)) => {}
166
167 Expr::Fn(FnExpr { function, .. })
168 if matches!(&**function, Function { body: Some(..), .. }) =>
169 {
170 if function.body.as_ref().unwrap().stmts.len() == 1
171 && matches!(&function.body.as_ref().unwrap().stmts[0], Stmt::Return(..))
172 {
173 } else {
174 log_abort!("inline: [x] It's not fn-local");
175 return;
176 }
177 }
178 _ => {
179 log_abort!("inline: [x] It's not fn-local");
180 return;
181 }
182 }
183 }
184
185 if !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) {
186 match init {
187 Expr::Fn(..) | Expr::Arrow(..) | Expr::Class(..) => {
188 self.typeofs.insert(ident.to_id(), atom!("function"));
189 }
190 Expr::Array(..) | Expr::Object(..) => {
191 self.typeofs.insert(ident.to_id(), atom!("object"));
192 }
193 _ => {}
194 }
195 }
196
197 if !usage.mutated() {
198 self.mode.store(ident.to_id(), &*init);
199 }
200
201 if usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY) {
202 return;
203 }
204
205 if is_inline_enabled
208 && usage.declared_count == 1
209 && usage.assign_count == 1
210 && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
211 && (usage.property_mutation_count == 0
212 || !usage.flags.contains(VarUsageInfoFlags::REASSIGNED))
213 && match init {
214 Expr::Ident(Ident { sym, .. }) if &**sym == "eval" => false,
215
216 Expr::Ident(id) if !id.eq_ignore_span(ident) => {
217 if !usage.flags.contains(VarUsageInfoFlags::ASSIGNED_FN_LOCAL) {
218 false
219 } else if let Some(u) = self.data.vars.get(&id.to_id()) {
220 let mut should_inline =
221 !u.flags.contains(VarUsageInfoFlags::REASSIGNED)
222 && u.flags.contains(VarUsageInfoFlags::DECLARED);
223
224 should_inline &=
225 !u.flags.contains(VarUsageInfoFlags::USED_ABOVE_DECL)
232 || !u.flags.contains(VarUsageInfoFlags::DECLARED_AS_FN_DECL)
233 || usage.callee_count == 0;
234
235 if u.flags.contains(VarUsageInfoFlags::DECLARED_AS_FOR_INIT)
236 && !usage.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL)
237 {
238 should_inline &= !matches!(
239 u.var_kind,
240 Some(VarDeclKind::Let | VarDeclKind::Const)
241 )
242 }
243
244 if u.flags.intersects(
245 VarUsageInfoFlags::DECLARED_AS_FN_DECL
246 .union(VarUsageInfoFlags::DECLARED_AS_FN_EXPR),
247 ) {
248 if self.options.keep_fnames
249 || self.mangle_options.is_some_and(|v| v.keep_fn_names)
250 {
251 should_inline = false
252 }
253 }
254
255 if u.flags.contains(VarUsageInfoFlags::DECLARED_AS_FN_EXPR) {
256 if self.options.inline != 3 {
257 return;
258 }
259 }
260
261 should_inline
262 } else {
263 false
264 }
265 }
266
267 Expr::Lit(lit) => match lit {
268 Lit::Str(s) => {
269 if (ref_count == 1
272 || (ref_count == 2
273 && usage.assign_count == 1
274 && usage.flags.intersects(VarUsageInfoFlags::LAZY_INIT)))
275 || s.value.len() <= 3
276 {
277 true
278 } else {
279 self.vars
280 .lits_for_cmp
281 .insert(ident.to_id(), init.clone().into());
282 false
283 }
284 }
285 Lit::Bool(_) | Lit::Null(_) | Lit::Num(_) | Lit::BigInt(_) => true,
286 Lit::Regex(_) => self.options.unsafe_regexp,
287 _ => false,
288 },
289 Expr::Unary(UnaryExpr {
290 op: op!("!"), arg, ..
291 }) => arg.is_lit(),
292 Expr::This(..) => usage.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL),
293 Expr::Arrow(arr) => {
294 !(usage.property_mutation_count > 0
295 || usage
296 .flags
297 .contains(VarUsageInfoFlags::EXECUTED_MULTIPLE_TIME)
298 || usage.flags.contains(VarUsageInfoFlags::USED_AS_ARG)
299 && ref_count > 1
300 || ref_count > usage.callee_count
301 || !is_arrow_simple_enough_for_copy(arr).is_some_and(|cost| cost <= 8))
302 }
303 _ => false,
304 }
305 {
306 if !inlined_into_init {
307 inlined_into_init = true;
308 self.vars.inline_with_multi_replacer(init);
309 }
310
311 self.mode.store(id.clone(), &*init);
312
313 let VarUsageInfo {
314 usage_count,
315 property_mutation_count,
316 flags,
317 ..
318 } = **usage;
319 let mut inc_usage = || {
320 for (i, _) in collect_infects_from(
321 &*init,
322 AliasConfig::default()
323 .marks(Some(self.marks))
324 .need_all(true),
325 ) {
326 if let Some(u) = self.data.vars.get_mut(&i) {
327 u.flags |= flags & VarUsageInfoFlags::USED_AS_ARG;
328 u.flags |= flags & VarUsageInfoFlags::USED_AS_REF;
329 u.flags |= flags & VarUsageInfoFlags::INDEXED_WITH_DYNAMIC_KEY;
330 u.flags |= flags & VarUsageInfoFlags::HAS_PROPERTY_ACCESS;
331 u.flags |= flags & VarUsageInfoFlags::USED_ABOVE_DECL;
332 u.flags |= flags & VarUsageInfoFlags::EXECUTED_MULTIPLE_TIME;
333 u.flags |= flags & VarUsageInfoFlags::USED_IN_COND;
334 u.flags |= flags & VarUsageInfoFlags::USED_RECURSIVELY;
335
336 if !flags.contains(VarUsageInfoFlags::NO_SIDE_EFFECT_FOR_MEMBER_ACCESS)
337 {
338 u.flags
339 .remove(VarUsageInfoFlags::NO_SIDE_EFFECT_FOR_MEMBER_ACCESS)
340 }
341
342 u.ref_count += ref_count;
343 u.usage_count += usage_count;
344 u.property_mutation_count += property_mutation_count;
345 }
346 }
347 };
348
349 if self.options.inline != 0
350 && may_remove
351 && match init {
352 Expr::Arrow(..) => self.options.unused,
353 _ => true,
354 }
355 {
356 self.changed = true;
357
358 report_change!(
359 "inline: Decided to inline '{}{:?}' because it's simple",
360 ident.sym,
361 ident.ctxt
362 );
363
364 inc_usage();
365
366 self.vars.lits.insert(id.clone(), init.take().into());
367
368 ident.take();
369 } else if self.options.inline != 0 || self.options.reduce_vars {
370 report_change!(
371 "inline: Decided to inline '{}{:?}' because it's simple",
372 ident.sym,
373 ident.ctxt
374 );
375
376 self.mode.store(id.clone(), &*init);
377
378 inc_usage();
379
380 self.vars.lits.insert(id.clone(), init.clone().into());
381 }
382 }
383
384 let usage = self.data.vars.get(&id).unwrap();
385
386 if !self.ctx.bit_ctx.contains(BitCtx::IsExported)
388 && is_inline_enabled
389 && usage.flags.contains(VarUsageInfoFlags::DECLARED)
390 && may_remove
391 && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
392 && !usage
393 .flags
394 .contains(VarUsageInfoFlags::DECLARED_AS_FOR_INIT)
395 && usage.assign_count == 1
396 && ref_count == 1
397 {
398 match init {
399 Expr::Fn(FnExpr { function: f, .. })
400 if matches!(
401 &**f,
402 Function { is_async: true, .. }
403 | Function {
404 is_generator: true,
405 ..
406 }
407 ) =>
408 {
409 return
410 }
411 Expr::Arrow(ArrowExpr { is_async: true, .. })
412 | Expr::Arrow(ArrowExpr {
413 is_generator: true, ..
414 }) => return,
415
416 Expr::Lit(Lit::Regex(..)) => {
417 if !usage.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL)
418 || usage
419 .flags
420 .contains(VarUsageInfoFlags::EXECUTED_MULTIPLE_TIME)
421 {
422 return;
423 }
424 }
425
426 Expr::This(..) => {
427 if !usage.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL) {
429 return;
430 }
431 }
432
433 Expr::Lit(..) => {}
434
435 Expr::Fn(_) | Expr::Arrow(..) if !usage.can_inline_fn_once() => {
436 return;
437 }
438
439 Expr::Fn(f) => {
440 let excluded: Vec<Id> = find_pat_ids(&f.function.params);
441
442 for id in idents_used_by(&f.function.params) {
443 if excluded.contains(&id) {
444 continue;
445 }
446 if let Some(v_usage) = self.data.vars.get(&id) {
447 if v_usage.flags.contains(VarUsageInfoFlags::REASSIGNED) {
448 return;
449 }
450 } else {
451 return;
452 }
453 }
454 }
455
456 Expr::Arrow(f) => {
457 let excluded: Vec<Id> = find_pat_ids(&f.params);
458
459 for id in idents_used_by(&f.params) {
460 if excluded.contains(&id) {
461 continue;
462 }
463 if let Some(v_usage) = self.data.vars.get(&id) {
464 if v_usage.flags.contains(VarUsageInfoFlags::REASSIGNED) {
465 return;
466 }
467 } else {
468 return;
469 }
470 }
471 }
472
473 Expr::Object(..) if self.options.pristine_globals => {
474 for id in idents_used_by_ignoring_nested(init) {
475 if let Some(v_usage) = self.data.vars.get(&id) {
476 if v_usage.flags.contains(VarUsageInfoFlags::REASSIGNED) {
477 return;
478 }
479 }
480 }
481 }
482
483 Expr::Ident(id) if !id.eq_ignore_span(ident) => {
484 if !usage.flags.contains(VarUsageInfoFlags::ASSIGNED_FN_LOCAL) {
485 return;
486 }
487
488 if let Some(init_usage) = self.data.vars.get(&id.to_id()) {
489 if init_usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
490 || !init_usage.flags.contains(VarUsageInfoFlags::DECLARED)
491 {
492 return;
493 }
494
495 if init_usage
496 .flags
497 .contains(VarUsageInfoFlags::DECLARED_AS_FN_DECL)
498 || init_usage
499 .flags
500 .contains(VarUsageInfoFlags::DECLARED_AS_FN_EXPR)
501 {
502 if self.options.keep_fnames
503 || self.mangle_options.is_some_and(|v| v.keep_fn_names)
504 {
505 return;
506 }
507 }
508 if init_usage
509 .flags
510 .contains(VarUsageInfoFlags::DECLARED_AS_FN_EXPR)
511 {
512 if self.options.inline != 3 {
513 return;
514 }
515 }
516 }
517 }
518
519 _ => {
520 for id in idents_used_by(init) {
521 if let Some(v_usage) = self.data.vars.get(&id) {
522 if v_usage.property_mutation_count > usage.property_mutation_count
523 || v_usage.flags.intersects(
524 VarUsageInfoFlags::HAS_PROPERTY_ACCESS
525 .union(VarUsageInfoFlags::REASSIGNED),
526 )
527 {
528 return;
529 }
530 }
531 }
532 }
533 }
534
535 if usage.flags.contains(VarUsageInfoFlags::USED_AS_ARG)
536 && !usage.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL)
537 {
538 if let Expr::Fn(..) | Expr::Arrow(..) = init {
539 return;
540 }
541 }
542
543 if usage
544 .flags
545 .contains(VarUsageInfoFlags::EXECUTED_MULTIPLE_TIME)
546 {
547 match init {
548 Expr::Lit(..) => {}
549 Expr::Fn(f) => {
550 let params: Vec<Id> = find_pat_ids(&f.function.params);
555
556 if !params.is_empty() {
557 let captured = idents_captured_by(&f.function.body);
558
559 for param in params {
560 if captured.contains(¶m) {
561 return;
562 }
563 }
564 }
565 }
566 _ => {
567 return;
568 }
569 }
570 }
571
572 if init.may_have_side_effects(self.ctx.expr_ctx) {
573 return;
574 }
575
576 if !inlined_into_init {
577 self.vars.inline_with_multi_replacer(init);
578 }
579
580 report_change!(
581 "inline: Decided to inline var '{}' because it's used only once",
582 ident
583 );
584 self.changed = true;
585
586 self.vars
587 .vars_for_inlining
588 .insert(ident.take().to_id(), init.take().into());
589 }
590 }
591 }
592
593 fn is_fn_body_simple_enough_to_inline(
595 &self,
596 body: &BlockStmt,
597 param_count: usize,
598 usage: &VarUsageInfo,
599 ) -> bool {
600 let param_cost = param_count * 2;
601 let func_body_cost = if usage.ref_count == usage.callee_count {
603 14 / usage.usage_count
605 } else {
606 0
607 } as usize;
608 let cost_limit = 3 + param_cost + func_body_cost;
609
610 if body.stmts.len() == 1 {
611 match &body.stmts[0] {
612 Stmt::Expr(ExprStmt { expr, .. })
613 if expr.size(self.ctx.expr_ctx.unresolved_ctxt) < cost_limit =>
614 {
615 return true
616 }
617
618 Stmt::Return(ReturnStmt { arg: Some(arg), .. })
619 if arg.size(self.ctx.expr_ctx.unresolved_ctxt) < cost_limit =>
620 {
621 return true
622 }
623
624 Stmt::Return(ReturnStmt { arg: None, .. }) => {
625 return 6 < cost_limit;
627 }
628
629 _ => {}
630 }
631 }
632
633 false
634 }
635
636 pub(super) fn store_typeofs(&mut self, decl: &mut Decl) {
638 let i = match &*decl {
639 Decl::Class(v) => v.ident.clone(),
640 Decl::Fn(f) => f.ident.clone(),
641 _ => return,
642 };
643 if i.sym == *"arguments" {
644 return;
645 }
646
647 if let Some(usage) = self.data.vars.get(&i.to_id()) {
648 if !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) {
649 trace_op!("typeofs: Storing typeof `{}{:?}`", i.sym, i.ctxt);
650 match &*decl {
651 Decl::Fn(..) | Decl::Class(..) => {
652 self.typeofs.insert(i.to_id(), atom!("function"));
653 }
654 _ => {}
655 }
656 }
657 }
658 }
659
660 pub(super) fn store_decl_for_inlining(&mut self, decl: &mut Decl) {
663 let i = match &*decl {
664 Decl::Class(v) => &v.ident,
665 Decl::Fn(f) => {
666 if f.function.is_async {
667 return;
668 }
669 &f.ident
670 }
671 _ => return,
672 };
673
674 trace_op!("inline: Trying to inline decl ({}{:?})", i.sym, i.ctxt);
675
676 if self.options.inline == 0 && !self.options.reduce_vars {
677 log_abort!("inline: [x] Inline disabled");
678 return;
679 }
680
681 if !self.may_remove_ident(i) {
682 log_abort!("inline: [x] Top level");
683 return;
684 }
685
686 if let Some(f) = decl.as_fn_decl() {
687 if self.has_noinline(f.function.ctxt) {
688 log_abort!("inline: [x] Has noinline");
689 return;
690 }
691 }
692
693 if self.ctx.bit_ctx.contains(BitCtx::IsExported) {
694 log_abort!("inline: [x] exported");
695 return;
696 }
697
698 if self
699 .data
700 .top
701 .intersects(ScopeData::HAS_EVAL_CALL.union(ScopeData::HAS_WITH_STMT))
702 {
703 return;
704 }
705
706 let id = i.to_id();
707
708 if let Some(usage) = self.data.vars.get(&id) {
709 if usage
710 .flags
711 .contains(VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM)
712 {
713 log_abort!("inline: Declared as a catch parameter");
714 return;
715 }
716
717 if usage.flags.contains(VarUsageInfoFlags::USED_AS_ARG) && usage.ref_count > 1 {
718 log_abort!("inline: Used as an arugment");
719 return;
720 }
721
722 if usage.flags.intersects(
723 VarUsageInfoFlags::REASSIGNED.union(VarUsageInfoFlags::INLINE_PREVENTED),
724 ) {
725 log_abort!(
726 "inline: [x] reassigned = {}, inline_prevented = {}",
727 usage.flags.contains(VarUsageInfoFlags::REASSIGNED),
728 usage.flags.contains(VarUsageInfoFlags::INLINE_PREVENTED)
729 );
730 return;
731 }
732
733 self.vars.inline_with_multi_replacer(decl);
735 match decl {
736 Decl::Fn(f) if self.options.inline >= 2 && f.ident.sym != *"arguments" => {
737 if let Some(body) = &f.function.body {
738 if !usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY)
739 && usage.callee_count > 0
741 && usage.ref_count > 1
743 && self.is_fn_body_simple_enough_to_inline(
744 body,
745 f.function.params.len(),
746 usage,
747 )
748 {
749 if f.function
750 .params
751 .iter()
752 .any(|param| matches!(param.pat, Pat::Rest(..) | Pat::Assign(..)))
753 {
754 return;
755 }
756 report_change!(
757 "inline: Decided to inline function `{}{:?}` as it's very simple",
758 id.0,
759 id.1
760 );
761
762 for i in collect_infects_from(
763 &f.function,
764 AliasConfig::default()
765 .marks(Some(self.marks))
766 .need_all(true),
767 ) {
768 if let Some(usage) = self.data.vars.get_mut(&i.0) {
769 usage.ref_count += 1;
770 }
771 }
772
773 self.vars.simple_functions.insert(
774 id,
775 FnExpr {
776 ident: None,
777 function: f.function.clone(),
778 }
779 .into(),
780 );
781
782 return;
783 }
784 }
785 }
786 _ => {}
787 }
788
789 if (self.options.reduce_vars || self.options.collapse_vars || self.options.inline != 0)
800 && usage.ref_count == 1
801 && usage.can_inline_fn_once()
802 && (match decl {
803 Decl::Class(..) => !usage.flags.contains(VarUsageInfoFlags::USED_ABOVE_DECL),
804 Decl::Fn(..) => true,
805 _ => false,
806 })
807 {
808 if let Decl::Class(ClassDecl { class, .. }) = decl {
809 if class_has_side_effect(self.ctx.expr_ctx, class) {
810 return;
811 }
812 }
813
814 match &decl {
815 Decl::Class(_c) => {
816 if self.options.inline != 3
817 || self.options.keep_classnames
818 || self.mangle_options.is_some_and(|v| v.keep_class_names)
819 {
820 log_abort!("inline: [x] Keep class names");
821 return;
822 }
823
824 self.changed = true;
825 report_change!(
826 "inline: Decided to inline class `{}{:?}` as it's used only once",
827 _c.ident.sym,
828 _c.ident.ctxt
829 );
830 }
831 Decl::Fn(_f) => {
832 if self.options.keep_fnames
833 || self.mangle_options.is_some_and(|v| v.keep_fn_names)
834 {
835 log_abort!("inline: [x] Keep fn names");
836 return;
837 }
838
839 self.changed = true;
840 report_change!(
841 "inline: Decided to inline function `{}{:?}` as it's used only once",
842 _f.ident.sym,
843 _f.ident.ctxt
844 );
845 }
846 _ => {}
847 }
848
849 let e = match decl.take() {
850 Decl::Class(c) => ClassExpr {
851 ident: Some(c.ident),
852 class: c.class,
853 }
854 .into(),
855 Decl::Fn(f) => FnExpr {
856 ident: if usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY) {
857 Some(f.ident)
858 } else {
859 None
860 },
861 function: f.function,
862 }
863 .into(),
864 _ => {
865 unreachable!()
866 }
867 };
868
869 self.vars.vars_for_inlining.insert(id, e);
870 } else {
871 log_abort!("inline: [x] Usage: {:?}", usage);
872 }
873 }
874 }
875
876 pub(super) fn inline(&mut self, e: &mut Expr) {
878 if self.ctx.bit_ctx.contains(BitCtx::IsExactLhsOfAssign) {
879 return;
880 }
881
882 match e {
883 Expr::Member(me) => {
884 if let MemberProp::Computed(prop) = &mut me.prop {
885 if let Expr::Lit(Lit::Num(..)) = &*prop.expr {
886 if let Expr::Ident(obj) = &*me.obj {
887 let new = self.vars.lits_for_array_access.get(&obj.to_id());
888
889 if let Some(new) = new {
890 report_change!("inline: Inlined array access");
891 self.changed = true;
892
893 me.obj.clone_from(new);
894 }
895 }
896 }
897 }
898 }
899 Expr::Ident(i) => {
900 let id = i.to_id();
901 if let Some(value) = self.vars.lits.get(&id).or_else(|| {
902 if self.ctx.bit_ctx.contains(BitCtx::IsCallee) {
903 self.vars.simple_functions.get(&id)
904 } else {
905 None
906 }
907 }) {
908 if !matches!(**value, Expr::Ident(..) | Expr::Member(..))
909 && self.ctx.bit_ctx.contains(BitCtx::IsUpdateArg)
910 {
911 return;
912 }
913
914 let bindings: FxHashSet<Id> = collect_decls(value);
917 let new_mark = Mark::new();
918 let mut cache = FxHashMap::default();
919 let mut remap = FxHashMap::default();
920
921 for id in bindings {
922 let new_ctxt = cache
923 .entry(id.1)
924 .or_insert_with(|| id.1.apply_mark(new_mark));
925
926 let new_ctxt = *new_ctxt;
927
928 if let Some(usage) = self.data.vars.get(&id).cloned() {
929 let new_id = (id.0.clone(), new_ctxt);
930 self.data.vars.insert(new_id, usage);
931 }
932
933 remap.insert(id, new_ctxt);
934 }
935
936 let mut value = value.clone();
937 if !remap.is_empty() {
938 let mut remapper = Remapper::new(&remap);
939 value.visit_mut_with(&mut remapper);
940 }
941
942 self.changed = true;
943 report_change!("inline: Replacing a variable `{}` with cheap expression", i);
944
945 *e = *value;
946 return;
947 }
948
949 if let Some(value) = self.vars.vars_for_inlining.get(&id) {
951 if self.ctx.bit_ctx.contains(BitCtx::IsExactLhsOfAssign)
952 && !is_valid_for_lhs(value)
953 {
954 return;
955 }
956
957 if let Expr::Member(..) = &**value {
958 if self.ctx.bit_ctx.contains(BitCtx::ExecutedMultipleTime) {
959 return;
960 }
961 }
962 }
963
964 if let Some(value) = self.vars.vars_for_inlining.remove(&id) {
965 self.changed = true;
966 report_change!("inline: Replacing '{}' with an expression", i);
967
968 *e = *value;
969
970 log_abort!("inline: [Change] {}", crate::debug::dump(&*e, false))
971 }
972 }
973 _ => (),
974 }
975 }
976}
977
978fn is_arrow_simple_enough_for_copy(e: &ArrowExpr) -> Option<u8> {
979 if e.is_async {
980 return None;
981 }
982
983 match &*e.body {
984 BlockStmtOrExpr::BlockStmt(s) => is_block_stmt_of_fn_simple_enough_for_copy(s),
985 BlockStmtOrExpr::Expr(e) => is_arrow_body_simple_enough_for_copy(e),
986 #[cfg(swc_ast_unknown)]
987 _ => panic!("unable to access unknown nodes"),
988 }
989}
990
991fn is_arrow_body_simple_enough_for_copy(e: &Expr) -> Option<u8> {
992 match e {
993 Expr::Ident(..) | Expr::Lit(..) => return Some(1),
994 Expr::Member(MemberExpr { obj, prop, .. }) if !prop.is_computed() => {
995 return Some(is_arrow_body_simple_enough_for_copy(obj)? + 2)
996 }
997 Expr::Call(c) => {
998 let mut cost = is_arrow_body_simple_enough_for_copy(c.callee.as_expr()?.deref())?;
999 for arg in &c.args {
1000 let arg_cost = is_arrow_body_simple_enough_for_copy(&arg.expr)?;
1001 cost += arg_cost;
1002 cost += if arg.spread.is_some() { 3 } else { 0 };
1003 }
1004
1005 return Some(cost + 2);
1006 }
1007 Expr::Unary(u) => return Some(is_arrow_body_simple_enough_for_copy(&u.arg)? + 1),
1008
1009 Expr::Bin(b) => {
1010 return Some(
1011 is_arrow_body_simple_enough_for_copy(&b.left)?
1012 + is_arrow_body_simple_enough_for_copy(&b.right)?
1013 + 2,
1014 )
1015 }
1016 _ => {}
1017 }
1018
1019 None
1020}
1021
1022fn is_block_stmt_of_fn_simple_enough_for_copy(b: &BlockStmt) -> Option<u8> {
1023 if b.stmts.len() == 1 {
1024 if let Stmt::Return(ret) = &b.stmts[0] {
1025 return ret
1026 .arg
1027 .as_deref()
1028 .map_or(Some(0), is_arrow_body_simple_enough_for_copy);
1029 }
1030 }
1031
1032 None
1033}