swc_ecma_minifier/compress/pure/
loops.rs1use 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 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 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 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 self.changed = true;
226 report_change!("loops: Compressing for-if-break into a for statement");
227
228 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 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}