swc_ecma_minifier/compress/pure/
loops.rs

1use std::mem::take;
2
3use swc_common::{util::take::Take, Spanned};
4use swc_ecma_ast::*;
5use swc_ecma_utils::{ExprExt, StmtExt, Value};
6
7use super::{DropOpts, Pure};
8
9impl Pure<'_> {
10    pub(super) fn optimize_for_init(&mut self, init: &mut Option<VarDeclOrExpr>) {
11        if !self.options.loops {
12            return;
13        }
14
15        if let Some(VarDeclOrExpr::Expr(e)) = init {
16            self.ignore_return_value(e, DropOpts::DROP_NUMBER.union(DropOpts::DROP_STR_LIT));
17
18            if e.is_invalid() {
19                *init = None;
20            }
21        }
22
23        if let Some(VarDeclOrExpr::Expr(e)) = init {
24            self.make_bool_short(e, false, true);
25        }
26    }
27
28    pub(super) fn optimize_for_update(&mut self, update: &mut Option<Box<Expr>>) {
29        if !self.options.loops {
30            return;
31        }
32
33        if let Some(e) = update {
34            self.ignore_return_value(e, DropOpts::DROP_NUMBER.union(DropOpts::DROP_STR_LIT));
35
36            if e.is_invalid() {
37                *update = None;
38                return;
39            }
40
41            self.make_bool_short(e, false, true);
42        }
43    }
44
45    pub(super) fn optimize_body_of_loop_stmt(&mut self, s: &mut Stmt) {
46        if !self.options.loops {
47            return;
48        }
49
50        match s {
51            Stmt::For(ForStmt { body, .. })
52            | Stmt::ForIn(ForInStmt { body, .. })
53            | Stmt::ForOf(ForOfStmt { body, .. })
54            | Stmt::While(WhileStmt { body, .. }) => {
55                optimize_loop_body(body);
56            }
57            _ => (),
58        }
59    }
60
61    ///
62    /// - `while(test);` => `for(;;test);
63    /// - `do; while(true)` => `for(;;);
64    pub(super) fn loop_to_for_stmt(&mut self, s: &mut Stmt) {
65        if !self.options.loops {
66            return;
67        }
68
69        match s {
70            Stmt::While(stmt) => {
71                self.changed = true;
72                report_change!("loops: Converting a while loop to a for loop");
73                *s = ForStmt {
74                    span: stmt.span,
75                    init: None,
76                    test: Some(stmt.test.take()),
77                    update: None,
78                    body: stmt.body.take(),
79                }
80                .into();
81            }
82            Stmt::DoWhile(stmt) => {
83                let val = stmt.test.as_pure_bool(self.expr_ctx);
84                if let Value::Known(true) = val {
85                    self.changed = true;
86                    report_change!("loops: Converting an always-true do-while loop to a for loop");
87
88                    *s = ForStmt {
89                        span: stmt.span,
90                        init: None,
91                        test: Some(stmt.test.take()),
92                        update: None,
93                        body: stmt.body.take(),
94                    }
95                    .into();
96                }
97            }
98            _ => {}
99        }
100    }
101
102    /// ## Input
103    /// ```js
104    /// for(; bar();){
105    ///     if (x(), y(), foo()) break;
106    ///     z(), k();
107    /// }
108    /// ```
109    ///
110    /// ## Output
111    ///
112    /// ```js
113    /// for(; bar() && (x(), y(), !foo());)z(), k();
114    /// ```
115    pub(super) fn optimize_for_if_break(&mut self, s: &mut ForStmt) -> Option<()> {
116        if !self.options.loops {
117            return None;
118        }
119
120        if let Stmt::Block(body) = &mut *s.body {
121            let first = body.stmts.get_mut(0)?;
122
123            if let Stmt::If(IfStmt {
124                span,
125                test,
126                cons,
127                alt: None,
128                ..
129            }) = first
130            {
131                if let Stmt::Break(BreakStmt { label: None, .. }) = &**cons {
132                    self.negate(test, false, false);
133
134                    match s.test.as_deref_mut() {
135                        Some(e) => {
136                            let orig_test = e.take();
137                            *e = BinExpr {
138                                span: *span,
139                                op: op!("&&"),
140                                left: Box::new(orig_test),
141                                right: test.take(),
142                            }
143                            .into();
144                        }
145                        None => {
146                            s.test = Some(test.take());
147                        }
148                    }
149
150                    report_change!("loops: Optimizing a for loop with an if-then-break");
151
152                    first.take();
153                    return None;
154                }
155            }
156
157            if let Stmt::If(IfStmt {
158                span,
159                test,
160                cons,
161                alt: Some(alt),
162                ..
163            }) = first
164            {
165                if let Stmt::Break(BreakStmt { label: None, .. }) = &**alt {
166                    match s.test.as_deref_mut() {
167                        Some(e) => {
168                            let orig_test = e.take();
169                            *e = BinExpr {
170                                span: *span,
171                                op: op!("&&"),
172                                left: Box::new(orig_test),
173                                right: test.take(),
174                            }
175                            .into();
176                        }
177                        None => {
178                            s.test = Some(test.take());
179                        }
180                    }
181
182                    report_change!("loops: Optimizing a for loop with an if-else-break");
183
184                    *first = *cons.take();
185                    return None;
186                }
187            }
188        }
189
190        None
191    }
192
193    /// # Input
194    ///
195    /// ```js
196    /// for(; size--;)
197    ///     if (!(result = eq(a[size], b[size], aStack, bStack)))
198    ///         break;
199    /// ```
200    ///
201    ///
202    /// # Output
203    ///
204    /// ```js
205    /// for (; size-- && (result = eq(a[size], b[size], aStack, bStack)););
206    /// ```
207    pub(super) fn merge_for_if_break(&mut self, s: &mut ForStmt) {
208        if let Stmt::If(IfStmt {
209            test,
210            cons,
211            alt: None,
212            ..
213        }) = &mut *s.body
214        {
215            if let Stmt::Break(BreakStmt { label: None, .. }) = &**cons {
216                // We only care about instant breaks.
217                //
218                // Note: As the minifier of swc is very fast, we don't
219                // care about block statements with a single break as a
220                // body.
221                //
222                // If it's optimizable, other pass for if statements
223                // will remove block and with the next pass we can apply
224                // this pass.
225                self.changed = true;
226                report_change!("loops: Compressing for-if-break into a for statement");
227
228                // We negate because this `test` is used as a condition for `break`.
229                self.negate(test, true, false);
230
231                match s.test.take() {
232                    Some(left) => {
233                        s.test = Some(
234                            BinExpr {
235                                span: s.test.span(),
236                                op: op!("&&"),
237                                left,
238                                right: test.take(),
239                            }
240                            .into(),
241                        );
242                    }
243                    None => {
244                        s.test = Some(test.take());
245                    }
246                }
247
248                // Remove body
249                s.body.take();
250            }
251        }
252    }
253
254    pub(super) fn optimize_loops_with_constant_condition(&mut self, s: &mut Stmt) {
255        if !self.options.loops {
256            return;
257        }
258
259        match s {
260            Stmt::DoWhile(stmt) => {
261                let val = stmt.test.as_pure_bool(self.expr_ctx);
262                if let Value::Known(false) = val {
263                    if should_not_inline_loop_body(&stmt.body, true) {
264                        return;
265                    }
266
267                    *s = take(&mut *stmt.body);
268                    report_change!(
269                        "loops: Removing a do while loop with a constant false condition (single \
270                         run)"
271                    );
272                    self.changed = true;
273                }
274            }
275
276            Stmt::While(stmt) => {
277                let val = stmt.test.as_pure_bool(self.expr_ctx);
278                if let Value::Known(false) = val {
279                    let var_ids = stmt.body.extract_var_ids_as_var();
280
281                    *s = var_ids
282                        .map(|decl| Stmt::Decl(Decl::Var(Box::new(decl))))
283                        .unwrap_or_else(Stmt::dummy);
284                    report_change!(
285                        "loops: Removing a while loop with a constant false condition (no run)"
286                    );
287                    self.changed = true;
288                }
289            }
290            _ => {}
291        }
292    }
293}
294
295fn optimize_loop_body(loop_body: &mut Stmt) {
296    if loop_body.is_empty() {
297        return;
298    }
299
300    if let Stmt::Continue(ContinueStmt { label: None, .. }) = loop_body {
301        *loop_body = Stmt::dummy();
302    }
303}
304
305fn should_not_inline_loop_body(s: &Stmt, allow_break_continue: bool) -> bool {
306    match s {
307        Stmt::Block(s) => s
308            .stmts
309            .iter()
310            .any(|s| should_not_inline_loop_body(s, allow_break_continue)),
311
312        Stmt::If(s) => {
313            should_not_inline_loop_body(&s.cons, false)
314                || s.alt
315                    .as_deref()
316                    .map(|s| should_not_inline_loop_body(s, false))
317                    .unwrap_or_default()
318        }
319        Stmt::Switch(s) => s
320            .cases
321            .iter()
322            .any(|c| c.cons.iter().any(|s| should_not_inline_loop_body(s, false))),
323
324        Stmt::Continue(ContinueStmt {
325            label: Some(..), ..
326        })
327        | Stmt::Break(BreakStmt {
328            label: Some(..), ..
329        }) => true,
330
331        Stmt::Break(..) | Stmt::Continue(..) => !allow_break_continue,
332
333        Stmt::Return(..)
334        | Stmt::Throw(..)
335        | Stmt::Empty(..)
336        | Stmt::Expr(..)
337        | Stmt::Debugger(..) => false,
338
339        _ => true,
340    }
341}