1use par_iter::prelude::*;
2use swc_common::{util::take::Take, EqIgnoreSpan, Spanned, DUMMY_SP};
3use swc_ecma_ast::*;
4use swc_ecma_utils::{extract_var_ids, ExprCtx, ExprExt, StmtExt, StmtLike, Value};
5use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
6
7use super::Pure;
8use crate::{
9 compress::util::is_fine_for_if_cons,
10 maybe_par,
11 util::{make_bool, ModuleItemExt},
12};
13
14impl Pure<'_> {
16 pub(super) fn simplify_assign_expr(&mut self, e: &mut Expr) {
17 match e {
18 Expr::Assign(AssignExpr {
19 op: op!("="),
20 left: AssignTarget::Simple(l),
21 right: r,
22 ..
23 }) if match &*l {
24 SimpleAssignTarget::Ident(l) => match &**r {
25 Expr::Ident(r) => l.sym == r.sym && l.ctxt == r.ctxt,
26 _ => false,
27 },
28 _ => false,
29 } =>
30 {
31 report_change!("Dropping assignment to the same variable");
32 self.changed = true;
33 *e = r.take().ident().unwrap().into();
34 }
35
36 Expr::Assign(AssignExpr {
37 op: op!("="),
38 left: AssignTarget::Pat(left),
39 right,
40 ..
41 }) if match &*left {
42 AssignTargetPat::Array(arr) => {
43 arr.elems.is_empty() || arr.elems.iter().all(|v| v.is_none())
44 }
45 _ => false,
46 } =>
47 {
48 report_change!("Dropping assignment to an empty array pattern");
49 self.changed = true;
50 *e = *right.take();
51 }
52
53 Expr::Assign(AssignExpr {
54 op: op!("="),
55 left: AssignTarget::Pat(left),
56 right,
57 ..
58 }) if match &*left {
59 AssignTargetPat::Object(obj) => obj.props.is_empty(),
60 _ => false,
61 } =>
62 {
63 report_change!("Dropping assignment to an empty object pattern");
64 self.changed = true;
65 *e = *right.take();
66 }
67
68 _ => {}
69 }
70 }
71
72 pub(super) fn handle_instant_break(&mut self, s: &mut Stmt) {
75 if let Stmt::Labeled(ls) = s {
76 match &*ls.body {
77 Stmt::Break(BreakStmt {
78 label: Some(label), ..
79 }) => {
80 if label.sym == ls.label.sym {
81 self.changed = true;
82 report_change!("Dropping instant break `{}`", label);
83 *s = Stmt::dummy();
84 }
85 }
86
87 Stmt::Break(BreakStmt { label: None, .. }) => {
88 self.changed = true;
89 report_change!("Dropping instant break without label");
90 *s = *ls.body.take();
91 }
92
93 _ => (),
94 }
95 }
96 }
97
98 pub(super) fn optimize_labeled_stmt(&mut self, s: &mut Stmt) -> Option<()> {
116 if !self.options.dead_code {
117 return None;
118 }
119
120 if let Stmt::Labeled(ls) = s {
121 if ls.body.is_empty() {
122 self.changed = true;
123 report_change!("Dropping an empty label statement: `{}`", ls.label);
124 *s = Stmt::dummy();
125 return None;
126 }
127
128 if let Stmt::Block(bs) = &mut *ls.body {
129 let first = bs.stmts.first_mut()?;
130
131 if let Stmt::If(IfStmt {
132 test,
133 cons,
134 alt: None,
135 ..
136 }) = first
137 {
138 if let Stmt::Break(BreakStmt {
139 label: Some(label), ..
140 }) = &**cons
141 {
142 if ls.label.sym == label.sym {
143 self.changed = true;
144 report_change!(
145 "Optimizing labeled stmt with a break to if statement: `{}`",
146 label
147 );
148
149 self.negate(test, true, false);
150 let test = test.take();
151
152 let mut cons = bs.take();
153 cons.stmts.remove(0);
154
155 ls.body = Box::new(
156 IfStmt {
157 span: ls.span,
158 test,
159 cons: Box::new(Stmt::Block(cons)),
160 alt: None,
161 }
162 .into(),
163 );
164 return None;
165 }
166 }
167 }
168
169 if let Stmt::If(IfStmt {
170 test,
171 cons,
172 alt: Some(alt),
173 ..
174 }) = first
175 {
176 if let Stmt::Break(BreakStmt {
177 label: Some(label), ..
178 }) = &**alt
179 {
180 if ls.label.sym == label.sym {
181 self.changed = true;
182 report_change!(
183 "Optimizing labeled stmt with a break in alt to if statement: {}",
184 ls.label
185 );
186
187 let test = test.take();
188 let cons = *cons.take();
189
190 let mut new_cons = bs.take();
191 new_cons.stmts[0] = cons;
192
193 ls.body = Box::new(
194 IfStmt {
195 span: ls.span,
196 test,
197 cons: Box::new(Stmt::Block(new_cons)),
198 alt: None,
199 }
200 .into(),
201 );
202 return None;
203 }
204 }
205 }
206 }
207 }
208
209 None
210 }
211
212 pub(super) fn drop_useless_continue(&mut self, s: &mut Stmt) {
214 match s {
215 Stmt::Labeled(ls) => {
216 let new = self.drop_useless_continue_inner(Some(ls.label.clone()), &mut ls.body);
217 if let Some(new) = new {
218 *s = new;
219 }
220 }
221
222 _ => {
223 let new = self.drop_useless_continue_inner(None, s);
224 if let Some(new) = new {
225 *s = new;
226 }
227 }
228 }
229 }
230
231 fn drop_useless_continue_inner(
233 &mut self,
234 label: Option<Ident>,
235 loop_stmt: &mut Stmt,
236 ) -> Option<Stmt> {
237 let body = match loop_stmt {
238 Stmt::While(ws) => &mut *ws.body,
239 Stmt::For(fs) => &mut *fs.body,
240 Stmt::ForIn(fs) => &mut *fs.body,
241 Stmt::ForOf(fs) => &mut *fs.body,
242 _ => return None,
243 };
244
245 if let Stmt::Block(b) = body {
246 let last = b.stmts.last_mut()?;
247
248 if let Stmt::Continue(last_cs) = last {
249 if last_cs.label.is_some() {
250 if label.eq_ignore_span(&last_cs.label) {
251 } else {
252 return None;
253 }
254 }
255 } else {
256 return None;
257 }
258 self.changed = true;
259 report_change!("Remove useless continue (last stmt of a loop)");
260 b.stmts.remove(b.stmts.len() - 1);
261
262 if let Some(label) = &label {
263 if !contains_label(b, label) {
264 return Some(loop_stmt.take());
265 }
266 }
267 }
268
269 None
270 }
271
272 pub(super) fn drop_unreachable_stmts<T>(&mut self, stmts: &mut Vec<T>)
273 where
274 T: StmtLike + ModuleItemExt + Take,
275 {
276 if !self.options.dead_code && !self.options.if_return {
277 return;
278 }
279
280 let idx = stmts.iter().position(|stmt| match stmt.as_stmt() {
281 Some(s) => s.terminates(),
282 _ => false,
283 });
284
285 if let Some(idx) = idx {
287 self.drop_duplicate_terminate(&mut stmts[..=idx]);
288
289 if idx == stmts.len() - 1 {
291 return;
292 }
293
294 if stmts
296 .iter()
297 .skip(idx + 1)
298 .all(|s| matches!(s.as_stmt(), Some(Stmt::Decl(Decl::Fn(_)))))
299 {
300 if let Some(Stmt::Return(ReturnStmt { arg: None, .. })) = stmts[idx].as_stmt() {
301 stmts.remove(idx);
303 }
304
305 return;
306 }
307
308 self.changed = true;
309
310 report_change!("Dropping statements after a control keyword");
311
312 let stmts_len = stmts.len();
313
314 let (decls, hoisted_fns, mut new_stmts) = stmts.iter_mut().skip(idx + 1).fold(
316 (
317 Vec::with_capacity(stmts_len),
318 Vec::<T>::with_capacity(stmts_len),
319 Vec::with_capacity(stmts_len),
320 ),
321 |(mut decls, mut hoisted_fns, mut new_stmts), stmt| {
322 match stmt.take().try_into_stmt() {
323 Ok(Stmt::Decl(Decl::Fn(f))) => {
324 hoisted_fns.push(T::from(Stmt::from(f)));
325 }
326 Ok(t) => {
327 let ids = extract_var_ids(&t).into_iter().map(|i| VarDeclarator {
328 span: i.span,
329 name: i.into(),
330 init: None,
331 definite: false,
332 });
333 decls.extend(ids);
334 }
335 Err(item) => new_stmts.push(item),
336 };
337 (decls, hoisted_fns, new_stmts)
338 },
339 );
340
341 if !decls.is_empty() {
342 new_stmts.push(T::from(Stmt::from(VarDecl {
343 span: DUMMY_SP,
344 kind: VarDeclKind::Var,
345 decls,
346 declare: false,
347 ..Default::default()
348 })));
349 }
350
351 new_stmts.extend(stmts.drain(..=idx));
352
353 new_stmts.extend(hoisted_fns);
354
355 *stmts = new_stmts;
356 }
357 }
358
359 fn drop_duplicate_terminate<T: StmtLike>(&mut self, stmts: &mut [T]) {
360 let (last, stmts) = stmts.split_last_mut().unwrap();
361
362 let last = match last.as_stmt() {
363 Some(s @ (Stmt::Break(_) | Stmt::Continue(_) | Stmt::Return(_) | Stmt::Throw(_))) => s,
364 _ => return,
365 };
366
367 fn drop<T: StmtLike>(stmt: &mut T, last: &Stmt, need_break: bool, ctx: ExprCtx) -> bool {
368 match stmt.as_stmt_mut() {
369 Some(s) if s.eq_ignore_span(last) => {
370 if need_break {
371 *s = BreakStmt {
372 label: None,
373 span: s.span(),
374 }
375 .into();
376 } else {
377 s.take();
378 }
379 true
380 }
381 Some(Stmt::If(i)) => {
382 let mut changed = false;
383 changed |= drop(&mut *i.cons, last, need_break, ctx);
384 if let Some(alt) = i.alt.as_mut() {
385 changed |= drop(&mut **alt, last, need_break, ctx);
386 }
387 changed
388 }
389 Some(Stmt::Try(t)) => {
390 let mut changed = false;
391 if let Some(stmt) = t.block.stmts.last_mut() {
393 let side_effect = match last {
394 Stmt::Break(_) | Stmt::Continue(_) => false,
395 Stmt::Return(ReturnStmt { arg: None, .. }) => false,
396 Stmt::Return(ReturnStmt { arg: Some(arg), .. }) => {
397 arg.may_have_side_effects(ctx)
398 }
399 Stmt::Throw(_) => true,
400 _ => unreachable!(),
401 };
402 if t.finalizer.is_none() && !side_effect {
403 changed |= drop(stmt, last, need_break, ctx)
404 }
405 }
406 if let Some(h) = t.handler.as_mut() {
407 if let Some(stmt) = h.body.stmts.last_mut() {
408 changed |= drop(stmt, last, need_break, ctx);
409 }
410 }
411 if let Some(f) = t.finalizer.as_mut() {
412 if let Some(stmt) = f.stmts.last_mut() {
413 changed |= drop(stmt, last, need_break, ctx);
414 }
415 }
416 changed
417 }
418 Some(Stmt::Switch(s)) if !last.is_break_stmt() && !need_break => {
419 let mut changed = false;
420 for case in s.cases.iter_mut() {
421 for stmt in case.cons.iter_mut() {
422 changed |= drop(stmt, last, true, ctx);
423 }
424 }
425
426 changed
427 }
428 Some(
429 Stmt::For(ForStmt { body, .. })
430 | Stmt::ForIn(ForInStmt { body, .. })
431 | Stmt::ForOf(ForOfStmt { body, .. })
432 | Stmt::While(WhileStmt { body, .. })
433 | Stmt::DoWhile(DoWhileStmt { body, .. }),
434 ) if !last.is_break_stmt() && !last.is_continue_stmt() && !need_break => {
435 if let Stmt::Block(b) = &mut **body {
436 let mut changed = false;
437 for stmt in b.stmts.iter_mut() {
438 changed |= drop(stmt, last, true, ctx);
439 }
440 changed
441 } else {
442 drop(&mut **body, last, true, ctx)
443 }
444 }
445 Some(Stmt::Block(b)) => {
446 if let Some(stmt) = b.stmts.last_mut() {
447 drop(stmt, last, need_break, ctx)
448 } else {
449 false
450 }
451 }
452 _ => false,
453 }
454 }
455
456 if let Some(before_last) = stmts.last_mut() {
457 if drop(before_last, last, false, self.expr_ctx) {
458 self.changed = true;
459
460 report_change!("Dropping control keyword in nested block");
461 }
462 }
463 }
464
465 pub(super) fn drop_useless_blocks<T>(&mut self, stmts: &mut Vec<T>)
466 where
467 T: StmtLike,
468 {
469 fn is_ok(b: &BlockStmt) -> bool {
470 maybe_par!(
471 b.stmts.iter().all(is_fine_for_if_cons),
472 *crate::LIGHT_TASK_PARALLELS
473 )
474 }
475
476 if maybe_par!(
477 stmts
478 .iter()
479 .all(|stmt| !matches!(stmt.as_stmt(), Some(Stmt::Block(b)) if is_ok(b))),
480 *crate::LIGHT_TASK_PARALLELS
481 ) {
482 return;
483 }
484
485 self.changed = true;
486 report_change!("Dropping useless block");
487
488 let old_stmts = stmts.take();
489
490 let new: Vec<T> = if old_stmts.len() >= *crate::LIGHT_TASK_PARALLELS {
491 old_stmts
492 .into_par_iter()
493 .flat_map(|stmt| match stmt.try_into_stmt() {
494 Ok(v) => match v {
495 Stmt::Block(v) if is_ok(&v) => {
496 let stmts = v.stmts;
497 maybe_par!(
498 stmts.into_iter().map(T::from).collect(),
499 *crate::LIGHT_TASK_PARALLELS
500 )
501 }
502 _ => vec![T::from(v)],
503 },
504 Err(v) => vec![v],
505 })
506 .collect()
507 } else {
508 let mut new = Vec::with_capacity(old_stmts.len() * 2);
509 old_stmts
510 .into_iter()
511 .for_each(|stmt| match stmt.try_into_stmt() {
512 Ok(v) => match v {
513 Stmt::Block(v) if is_ok(&v) => {
514 new.extend(v.stmts.into_iter().map(T::from));
515 }
516 _ => new.push(T::from(v)),
517 },
518 Err(v) => new.push(v),
519 });
520 new
521 };
522 *stmts = new;
523 }
524
525 pub(super) fn drop_unused_stmt_at_end_of_fn(&mut self, s: &mut Stmt) {
526 if let Stmt::Return(r) = s {
527 if let Some(Expr::Unary(UnaryExpr {
528 span,
529 op: op!("void"),
530 arg,
531 })) = r.arg.as_deref_mut()
532 {
533 report_change!("unused: Removing `return void` in end of a function");
534 self.changed = true;
535 *s = ExprStmt {
536 span: *span,
537 expr: arg.take(),
538 }
539 .into();
540 }
541 }
542 }
543
544 pub(super) fn optimize_const_if<T>(&mut self, stmts: &mut Vec<T>)
545 where
546 T: StmtLike,
547 {
548 if !self.options.unused && !self.options.dead_code {
549 return;
550 }
551
552 if !maybe_par!(
553 stmts.iter().any(|stmt| match stmt.as_stmt() {
554 Some(Stmt::If(s)) => s.test.cast_to_bool(self.expr_ctx).1.is_known(),
555 _ => false,
556 }),
557 *crate::LIGHT_TASK_PARALLELS
558 ) {
559 return;
560 }
561
562 self.changed = true;
563 report_change!("dead_code: Removing dead codes");
564
565 let mut new = Vec::with_capacity(stmts.len());
566 stmts
567 .take()
568 .into_iter()
569 .for_each(|stmt| match stmt.try_into_stmt() {
570 Ok(stmt) => match stmt {
571 Stmt::If(mut s) => {
572 if let Value::Known(v) = s.test.cast_to_bool(self.expr_ctx).1 {
573 let mut var_ids = Vec::new();
574 new.push(T::from(
575 ExprStmt {
576 span: DUMMY_SP,
577 expr: s.test.take(),
578 }
579 .into(),
580 ));
581
582 if v {
583 if let Some(alt) = s.alt.take() {
584 var_ids = alt
585 .extract_var_ids()
586 .into_iter()
587 .map(|name| VarDeclarator {
588 span: DUMMY_SP,
589 name: name.into(),
590 init: None,
591 definite: Default::default(),
592 })
593 .collect();
594 }
595 if !var_ids.is_empty() {
596 new.push(T::from(
597 VarDecl {
598 span: DUMMY_SP,
599 kind: VarDeclKind::Var,
600 declare: Default::default(),
601 decls: var_ids,
602 ..Default::default()
603 }
604 .into(),
605 ))
606 }
607 new.push(T::from(*s.cons.take()));
608 } else {
609 var_ids = s
610 .cons
611 .extract_var_ids()
612 .into_iter()
613 .map(|name| VarDeclarator {
614 span: DUMMY_SP,
615 name: name.into(),
616 init: None,
617 definite: Default::default(),
618 })
619 .collect();
620 if !var_ids.is_empty() {
621 new.push(T::from(
622 VarDecl {
623 span: DUMMY_SP,
624 kind: VarDeclKind::Var,
625 declare: Default::default(),
626 decls: var_ids,
627 ..Default::default()
628 }
629 .into(),
630 ))
631 }
632 if let Some(alt) = s.alt.take() {
633 new.push(T::from(*alt));
634 }
635 }
636 } else {
637 new.push(T::from(s.into()));
638 }
639 }
640 _ => new.push(T::from(stmt)),
641 },
642 Err(stmt) => new.push(stmt),
643 });
644
645 *stmts = new;
646 }
647
648 pub(super) fn handle_known_delete(&mut self, e: &mut Expr) {
649 if !self.options.conditionals && !self.options.evaluate && !self.options.sequences() {
650 return;
651 }
652
653 let Expr::Unary(UnaryExpr {
654 op: op!("delete"),
655 arg,
656 ..
657 }) = e
658 else {
659 return;
660 };
661
662 match &**arg {
663 Expr::Ident(i) => {
664 if matches!(&*i.sym, "undefined" | "NaN" | "Infinity") {
665 *e = make_bool(i.span, false);
666 }
667 }
668
669 Expr::Unary(..) | Expr::Bin(..) | Expr::Cond(..) => {
670 *e = make_bool(e.span(), true);
671 }
672
673 _ => (),
674 }
675 }
676}
677
678fn contains_label<N>(node: &N, label: &Ident) -> bool
679where
680 for<'aa> N: VisitWith<LabelFinder<'aa>>,
681{
682 let mut v = LabelFinder {
683 label,
684 found: false,
685 };
686 node.visit_with(&mut v);
687 v.found
688}
689
690struct LabelFinder<'a> {
691 label: &'a Ident,
692 found: bool,
693}
694impl Visit for LabelFinder<'_> {
695 noop_visit_type!(fail);
696
697 fn visit_break_stmt(&mut self, s: &BreakStmt) {
698 if let Some(label) = &s.label {
699 if label.sym == self.label.sym {
700 self.found = true;
701 }
702 }
703 }
704
705 fn visit_continue_stmt(&mut self, s: &ContinueStmt) {
706 if let Some(label) = &s.label {
707 if label.sym == self.label.sym {
708 self.found = true;
709 }
710 }
711 }
712}