Skip to content

Commit ba8976d

Browse files
committed
refactor(minifier): change AST in-place instead of returning Option<Expression>
1 parent 3e71d2d commit ba8976d

File tree

6 files changed

+138
-160
lines changed

6 files changed

+138
-160
lines changed

crates/oxc_minifier/src/peephole/minimize_conditions.rs

Lines changed: 68 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -20,46 +20,26 @@ impl<'a> PeepholeOptimizations {
2020
expr: &mut Expression<'a>,
2121
ctx: &mut Ctx<'a, '_>,
2222
) {
23-
let mut changed = false;
24-
loop {
25-
let mut local_change = false;
26-
if let Some(folded_expr) = match expr {
27-
Expression::UnaryExpression(e) => self.try_minimize_not(e, ctx),
28-
Expression::BinaryExpression(e) => {
29-
if Self::try_compress_is_loose_boolean(e, ctx) {
30-
local_change = true;
31-
}
32-
Self::try_minimize_binary(e, ctx)
33-
}
34-
Expression::LogicalExpression(e) => self.minimize_logical_expression(e, ctx),
35-
Expression::ConditionalExpression(logical_expr) => {
36-
if self.try_fold_expr_in_boolean_context(&mut logical_expr.test, ctx) {
37-
local_change = true;
38-
}
39-
self.try_minimize_conditional(logical_expr, ctx)
40-
}
41-
Expression::AssignmentExpression(e) => {
42-
if self.try_compress_normal_assignment_to_combined_logical_assignment(e, ctx) {
43-
local_change = true;
44-
}
45-
if Self::try_compress_normal_assignment_to_combined_assignment(e, ctx) {
46-
local_change = true;
47-
}
48-
Self::try_compress_assignment_to_update_expression(e, ctx)
23+
match expr {
24+
Expression::UnaryExpression(_) => self.try_minimize_not(expr, ctx),
25+
Expression::BinaryExpression(e) => {
26+
Self::try_compress_is_loose_boolean(e, ctx);
27+
Self::try_minimize_binary(expr, ctx);
28+
}
29+
Expression::LogicalExpression(_) => self.minimize_logical_expression(expr, ctx),
30+
Expression::ConditionalExpression(logical_expr) => {
31+
self.try_fold_expr_in_boolean_context(&mut logical_expr.test, ctx);
32+
if let Some(changed) = self.try_minimize_conditional(logical_expr, ctx) {
33+
*expr = changed;
34+
ctx.state.changed = true;
4935
}
50-
_ => None,
51-
} {
52-
*expr = folded_expr;
53-
local_change = true;
5436
}
55-
if local_change {
56-
changed = true;
57-
} else {
58-
break;
37+
Expression::AssignmentExpression(e) => {
38+
self.try_compress_normal_assignment_to_combined_logical_assignment(e, ctx);
39+
Self::try_compress_normal_assignment_to_combined_assignment(e, ctx);
40+
Self::try_compress_assignment_to_update_expression(expr, ctx);
5941
}
60-
}
61-
if changed {
62-
ctx.state.changed = true;
42+
_ => {}
6343
}
6444
}
6545

@@ -103,9 +83,9 @@ impl<'a> PeepholeOptimizations {
10383
}
10484
// "a op b" => "a op b"
10585
// "(a op b) op c" => "(a op b) op c"
106-
let mut logic_expr = ctx.ast.logical_expression(span, a, op, b);
107-
self.minimize_logical_expression(&mut logic_expr, ctx)
108-
.unwrap_or_else(|| Expression::LogicalExpression(ctx.ast.alloc(logic_expr)))
86+
let mut logic_expr = ctx.ast.expression_logical(span, a, op, b);
87+
self.minimize_logical_expression(&mut logic_expr, ctx);
88+
logic_expr
10989
}
11090

11191
// `typeof foo === 'number'` -> `typeof foo == 'number'`
@@ -115,17 +95,15 @@ impl<'a> PeepholeOptimizations {
11595
// ^^^^^^^^^^^^^^ `ctx.expression_value_type(&e.left).is_boolean()` is `true`.
11696
// `x >> +y !== 0` -> `x >> +y`
11797
// ^^^^^^^ ctx.expression_value_type(&e.left).is_number()` is `true`.
118-
fn try_minimize_binary(
119-
e: &mut BinaryExpression<'a>,
120-
ctx: &mut Ctx<'a, '_>,
121-
) -> Option<Expression<'a>> {
98+
fn try_minimize_binary(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
99+
let Expression::BinaryExpression(e) = expr else { return };
122100
if !e.operator.is_equality() {
123-
return None;
101+
return;
124102
}
125103
let left = e.left.value_type(ctx);
126104
let right = e.right.value_type(ctx);
127105
if left.is_undetermined() || right.is_undetermined() {
128-
return None;
106+
return;
129107
}
130108
if left == right {
131109
match e.operator {
@@ -139,12 +117,14 @@ impl<'a> PeepholeOptimizations {
139117
}
140118
}
141119
if !left.is_boolean() {
142-
return None;
120+
return;
143121
}
144122
if e.right.may_have_side_effects(ctx) {
145-
return None;
123+
return;
146124
}
147-
let mut b = e.right.evaluate_value(ctx).and_then(ConstantValue::into_boolean)?;
125+
let Some(mut b) = e.right.evaluate_value(ctx).and_then(ConstantValue::into_boolean) else {
126+
return;
127+
};
148128
match e.operator {
149129
BinaryOperator::Inequality | BinaryOperator::StrictInequality => {
150130
e.operator = BinaryOperator::Equality;
@@ -154,14 +134,15 @@ impl<'a> PeepholeOptimizations {
154134
e.operator = BinaryOperator::Equality;
155135
}
156136
BinaryOperator::Equality => {}
157-
_ => return None,
137+
_ => return,
158138
}
159-
Some(if b {
139+
*expr = if b {
160140
e.left.take_in(ctx.ast)
161141
} else {
162142
let argument = e.left.take_in(ctx.ast);
163143
ctx.ast.expression_unary(e.span, UnaryOperator::LogicalNot, argument)
164-
})
144+
};
145+
ctx.state.changed = true;
165146
}
166147

167148
/// Compress `foo == true` into `foo == 1`.
@@ -171,19 +152,19 @@ impl<'a> PeepholeOptimizations {
171152
///
172153
/// In `IsLooselyEqual`, `true` and `false` are converted to `1` and `0` first.
173154
/// <https://tc39.es/ecma262/multipage/abstract-operations.html#sec-islooselyequal>
174-
fn try_compress_is_loose_boolean(e: &mut BinaryExpression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
155+
fn try_compress_is_loose_boolean(e: &mut BinaryExpression<'a>, ctx: &mut Ctx<'a, '_>) {
175156
if !matches!(e.operator, BinaryOperator::Equality | BinaryOperator::Inequality) {
176-
return false;
157+
return;
177158
}
178-
179159
if let Some(ConstantValue::Boolean(left_bool)) = e.left.evaluate_value(ctx) {
180160
e.left = ctx.ast.expression_numeric_literal(
181161
e.left.span(),
182162
if left_bool { 1.0 } else { 0.0 },
183163
None,
184164
NumberBase::Decimal,
185165
);
186-
return true;
166+
ctx.state.changed = true;
167+
return;
187168
}
188169
if let Some(ConstantValue::Boolean(right_bool)) = e.right.evaluate_value(ctx) {
189170
e.right = ctx.ast.expression_numeric_literal(
@@ -192,9 +173,8 @@ impl<'a> PeepholeOptimizations {
192173
None,
193174
NumberBase::Decimal,
194175
);
195-
return true;
176+
ctx.state.changed = true;
196177
}
197-
false
198178
}
199179

200180
/// Returns the identifier or the assignment target's identifier of the given expression.
@@ -222,15 +202,15 @@ impl<'a> PeepholeOptimizations {
222202
&self,
223203
expr: &mut AssignmentExpression<'a>,
224204
ctx: &mut Ctx<'a, '_>,
225-
) -> bool {
205+
) {
226206
if ctx.options().target < ESTarget::ES2020 {
227-
return false;
207+
return;
228208
}
229209
if !matches!(expr.operator, AssignmentOperator::Assign) {
230-
return false;
210+
return;
231211
}
232212

233-
let Expression::LogicalExpression(logical_expr) = &mut expr.right else { return false };
213+
let Expression::LogicalExpression(logical_expr) = &mut expr.right else { return };
234214
// NOTE: if the right hand side is an anonymous function, applying this compression will
235215
// set the `name` property of that function.
236216
// Since codes relying on the fact that function's name is undefined should be rare,
@@ -241,64 +221,64 @@ impl<'a> PeepholeOptimizations {
241221
Expression::Identifier(read_id_ref),
242222
) = (&expr.left, &logical_expr.left)
243223
else {
244-
return false;
224+
return;
245225
};
246226
// It should also early return when the reference might refer to a reference value created by a with statement
247227
// when the minifier supports with statements
248228
if write_id_ref.name != read_id_ref.name || ctx.is_global_reference(write_id_ref) {
249-
return false;
229+
return;
250230
}
251231

252232
let new_op = logical_expr.operator.to_assignment_operator();
253233
expr.operator = new_op;
254234
expr.right = logical_expr.right.take_in(ctx.ast);
255-
true
235+
ctx.state.changed = true;
256236
}
257237

258238
/// Compress `a = a + b` to `a += b`
259239
fn try_compress_normal_assignment_to_combined_assignment(
260240
expr: &mut AssignmentExpression<'a>,
261241
ctx: &mut Ctx<'a, '_>,
262-
) -> bool {
242+
) {
263243
if !matches!(expr.operator, AssignmentOperator::Assign) {
264-
return false;
244+
return;
265245
}
266-
267-
let Expression::BinaryExpression(binary_expr) = &mut expr.right else { return false };
268-
let Some(new_op) = binary_expr.operator.to_assignment_operator() else { return false };
269-
246+
let Expression::BinaryExpression(binary_expr) = &mut expr.right else { return };
247+
let Some(new_op) = binary_expr.operator.to_assignment_operator() else { return };
270248
if !Self::has_no_side_effect_for_evaluation_same_target(&expr.left, &binary_expr.left, ctx)
271249
{
272-
return false;
250+
return;
273251
}
274-
275252
expr.operator = new_op;
276253
expr.right = binary_expr.right.take_in(ctx.ast);
277-
true
254+
ctx.state.changed = true;
278255
}
279256

280257
/// Compress `a -= 1` to `--a` and `a -= -1` to `++a`
281258
#[expect(clippy::float_cmp)]
282259
fn try_compress_assignment_to_update_expression(
283-
expr: &mut AssignmentExpression<'a>,
260+
expr: &mut Expression<'a>,
284261
ctx: &mut Ctx<'a, '_>,
285-
) -> Option<Expression<'a>> {
286-
if !matches!(expr.operator, AssignmentOperator::Subtraction) {
287-
return None;
262+
) {
263+
let Expression::AssignmentExpression(e) = expr else { return };
264+
if !matches!(e.operator, AssignmentOperator::Subtraction) {
265+
return;
288266
}
289-
let Expression::NumericLiteral(num) = &expr.right else {
290-
return None;
291-
};
292-
let target = expr.left.as_simple_assignment_target_mut()?;
293-
let operator = if num.value == 1.0 {
294-
UpdateOperator::Decrement
295-
} else if num.value == -1.0 {
296-
UpdateOperator::Increment
267+
let operator = if let Expression::NumericLiteral(num) = &e.right {
268+
if num.value == 1.0 {
269+
UpdateOperator::Decrement
270+
} else if num.value == -1.0 {
271+
UpdateOperator::Increment
272+
} else {
273+
return;
274+
}
297275
} else {
298-
return None;
276+
return;
299277
};
278+
let Some(target) = e.left.as_simple_assignment_target_mut() else { return };
300279
let target = target.take_in(ctx.ast);
301-
Some(ctx.ast.expression_update(expr.span, operator, true, target))
280+
*expr = ctx.ast.expression_update(e.span, operator, true, target);
281+
ctx.state.changed = true;
302282
}
303283
}
304284

crates/oxc_minifier/src/peephole/minimize_expression_in_boolean_context.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl<'a> PeepholeOptimizations {
3333
&self,
3434
expr: &mut Expression<'a>,
3535
ctx: &mut Ctx<'a, '_>,
36-
) -> bool {
36+
) {
3737
match expr {
3838
// "!!a" => "a"
3939
Expression::UnaryExpression(u1) if u1.operator.is_not() => {
@@ -42,7 +42,7 @@ impl<'a> PeepholeOptimizations {
4242
let mut e = u2.argument.take_in(ctx.ast);
4343
self.try_fold_expr_in_boolean_context(&mut e, ctx);
4444
*expr = e;
45-
return true;
45+
ctx.state.changed = true;
4646
}
4747
}
4848
}
@@ -62,7 +62,7 @@ impl<'a> PeepholeOptimizations {
6262
// `if ((a | b) === 0);", "if (!(a | b));")`
6363
ctx.ast.expression_unary(e.span, UnaryOperator::LogicalNot, argument)
6464
};
65-
return true;
65+
ctx.state.changed = true;
6666
}
6767
// "if (!!a && !!b)" => "if (a && b)"
6868
Expression::LogicalExpression(e) if e.operator.is_and() => {
@@ -71,7 +71,7 @@ impl<'a> PeepholeOptimizations {
7171
// "if (anything && truthyNoSideEffects)" => "if (anything)"
7272
if e.right.get_side_free_boolean_value(ctx) == Some(true) {
7373
*expr = e.left.take_in(ctx.ast);
74-
return true;
74+
ctx.state.changed = true;
7575
}
7676
}
7777
// "if (!!a ||!!b)" => "if (a || b)"
@@ -81,7 +81,7 @@ impl<'a> PeepholeOptimizations {
8181
// "if (anything || falsyNoSideEffects)" => "if (anything)"
8282
if e.right.get_side_free_boolean_value(ctx) == Some(false) {
8383
*expr = e.left.take_in(ctx.ast);
84-
return true;
84+
ctx.state.changed = true;
8585
}
8686
}
8787
Expression::ConditionalExpression(e) => {
@@ -100,7 +100,8 @@ impl<'a> PeepholeOptimizations {
100100
(LogicalOperator::And, self.minimize_not(left.span(), left, ctx))
101101
};
102102
*expr = self.join_with_left_associative_op(span, op, left, right, ctx);
103-
return true;
103+
ctx.state.changed = true;
104+
return;
104105
}
105106
if let Some(boolean) = e.alternate.get_side_free_boolean_value(ctx) {
106107
let left = e.test.take_in(ctx.ast);
@@ -114,17 +115,16 @@ impl<'a> PeepholeOptimizations {
114115
(LogicalOperator::And, left)
115116
};
116117
*expr = self.join_with_left_associative_op(span, op, left, right, ctx);
117-
return true;
118+
ctx.state.changed = true;
118119
}
119120
}
120121
Expression::SequenceExpression(seq_expr) => {
121122
if let Some(last) = seq_expr.expressions.last_mut() {
122-
return self.try_fold_expr_in_boolean_context(last, ctx);
123+
self.try_fold_expr_in_boolean_context(last, ctx);
123124
}
124125
}
125126
_ => {}
126127
}
127-
false
128128
}
129129
}
130130

0 commit comments

Comments
 (0)