Skip to content

Commit 43f90e9

Browse files
committed
refactor(minifier): change AST in-place instead of returning Option<Expression>
1 parent 166f5cc commit 43f90e9

File tree

1 file changed

+114
-125
lines changed

1 file changed

+114
-125
lines changed

crates/oxc_minifier/src/peephole/fold_constants.rs

Lines changed: 114 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use oxc_allocator::TakeIn;
22
use oxc_ast::ast::*;
33
use oxc_ecmascript::{
4-
ToJsString,
4+
GlobalContext, ToJsString,
55
constant_evaluation::{ConstantEvaluation, ConstantValue, DetermineValueType, ValueType},
66
side_effects::MayHaveSideEffects,
77
};
@@ -16,112 +16,107 @@ impl<'a> PeepholeOptimizations {
1616
/// Constant Folding
1717
///
1818
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeFoldConstants.java>
19-
pub fn fold_constants_exit_expression(&self, expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
20-
match expr {
19+
pub fn fold_constants_exit_expression(&self, e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
20+
match e {
2121
Expression::TemplateLiteral(t) => {
2222
self.try_inline_values_in_template_literal(t, ctx);
2323
}
2424
Expression::ObjectExpression(e) => self.fold_object_spread(e, ctx),
25+
Expression::BinaryExpression(_) => {
26+
Self::try_fold_binary_expr(e, ctx);
27+
Self::try_fold_binary_typeof_comparison(e, ctx);
28+
}
29+
Expression::UnaryExpression(_) => Self::try_fold_unary_expr(e, ctx),
30+
Expression::StaticMemberExpression(_) => Self::try_fold_static_member_expr(e, ctx),
31+
Expression::ComputedMemberExpression(_) => Self::try_fold_computed_member_expr(e, ctx),
32+
Expression::LogicalExpression(_) => Self::try_fold_logical_expr(e, ctx),
33+
Expression::ChainExpression(_) => Self::try_fold_optional_chain(e, ctx),
34+
Expression::CallExpression(_) => Self::try_fold_number_constructor(e, ctx),
2535
_ => {}
2636
}
27-
28-
if let Some(folded_expr) = match expr {
29-
Expression::BinaryExpression(e) => Self::try_fold_binary_expr(e, ctx)
30-
.or_else(|| Self::try_fold_binary_typeof_comparison(e, ctx)),
31-
Expression::UnaryExpression(e) => Self::try_fold_unary_expr(e, ctx),
32-
Expression::StaticMemberExpression(e) => Self::try_fold_static_member_expr(e, ctx),
33-
Expression::ComputedMemberExpression(e) => Self::try_fold_computed_member_expr(e, ctx),
34-
Expression::LogicalExpression(e) => Self::try_fold_logical_expr(e, ctx),
35-
Expression::ChainExpression(e) => Self::try_fold_optional_chain(e, ctx),
36-
Expression::CallExpression(e) => Self::try_fold_number_constructor(e, ctx),
37-
_ => None,
38-
} {
39-
*expr = folded_expr;
40-
ctx.state.changed = true;
41-
}
4237
}
4338

4439
#[expect(clippy::float_cmp)]
45-
fn try_fold_unary_expr(
46-
e: &UnaryExpression<'a>,
47-
ctx: &mut Ctx<'a, '_>,
48-
) -> Option<Expression<'a>> {
40+
fn try_fold_unary_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
41+
let Expression::UnaryExpression(e) = expr else { return };
4942
match e.operator {
5043
// Do not fold `void 0` back to `undefined`.
51-
UnaryOperator::Void if e.argument.is_number_0() => None,
44+
UnaryOperator::Void if e.argument.is_number_0() => {}
5245
// Do not fold `true` and `false` back to `!0` and `!1`
53-
UnaryOperator::LogicalNot if matches!(&e.argument, Expression::NumericLiteral(lit) if lit.value == 0.0 || lit.value == 1.0) => {
54-
None
55-
}
46+
UnaryOperator::LogicalNot if matches!(&e.argument, Expression::NumericLiteral(lit) if lit.value == 0.0 || lit.value == 1.0) =>
47+
{}
5648
// Do not fold big int.
57-
UnaryOperator::UnaryNegation if e.argument.is_big_int_literal() => None,
49+
UnaryOperator::UnaryNegation if e.argument.is_big_int_literal() => {}
50+
_ if e.may_have_side_effects(ctx) => {}
5851
_ => {
59-
if e.may_have_side_effects(ctx) {
60-
None
61-
} else {
62-
e.evaluate_value(ctx).map(|v| ctx.value_to_expr(e.span, v))
52+
if let Some(changed) = e.evaluate_value(ctx).map(|v| ctx.value_to_expr(e.span, v)) {
53+
*expr = changed;
54+
ctx.state.changed = true;
6355
}
6456
}
6557
}
6658
}
6759

68-
fn try_fold_static_member_expr(
69-
e: &StaticMemberExpression<'a>,
70-
ctx: &mut Ctx<'a, '_>,
71-
) -> Option<Expression<'a>> {
60+
fn try_fold_static_member_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
61+
let Expression::StaticMemberExpression(e) = expr else { return };
7262
// TODO: tryFoldObjectPropAccess(n, left, name)
7363
if e.object.may_have_side_effects(ctx) {
74-
None
75-
} else {
76-
e.evaluate_value(ctx).map(|value| ctx.value_to_expr(e.span, value))
64+
return;
65+
}
66+
if let Some(changed) = e.evaluate_value(ctx).map(|value| ctx.value_to_expr(e.span, value)) {
67+
*expr = changed;
68+
ctx.state.changed = true;
7769
}
7870
}
7971

80-
fn try_fold_computed_member_expr(
81-
e: &ComputedMemberExpression<'a>,
82-
ctx: &mut Ctx<'a, '_>,
83-
) -> Option<Expression<'a>> {
72+
fn try_fold_computed_member_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
73+
let Expression::ComputedMemberExpression(e) = expr else { return };
8474
// TODO: tryFoldObjectPropAccess(n, left, name)
8575
if e.object.may_have_side_effects(ctx) || e.expression.may_have_side_effects(ctx) {
86-
None
87-
} else {
88-
e.evaluate_value(ctx).map(|value| ctx.value_to_expr(e.span, value))
76+
return;
77+
}
78+
if let Some(changed) = e.evaluate_value(ctx).map(|value| ctx.value_to_expr(e.span, value)) {
79+
*expr = changed;
80+
ctx.state.changed = true;
8981
}
9082
}
9183

92-
fn try_fold_logical_expr(
93-
logical_expr: &mut LogicalExpression<'a>,
94-
ctx: &mut Ctx<'a, '_>,
95-
) -> Option<Expression<'a>> {
96-
match logical_expr.operator {
97-
LogicalOperator::And | LogicalOperator::Or => Self::try_fold_and_or(logical_expr, ctx),
98-
LogicalOperator::Coalesce => Self::try_fold_coalesce(logical_expr, ctx),
84+
fn try_fold_logical_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
85+
let Expression::LogicalExpression(e) = expr else { return };
86+
if let Some(changed) = match e.operator {
87+
LogicalOperator::And | LogicalOperator::Or => Self::try_fold_and_or(e, ctx),
88+
LogicalOperator::Coalesce => Self::try_fold_coalesce(e, ctx),
89+
} {
90+
*expr = changed;
91+
ctx.state.changed = true;
9992
}
10093
}
10194

102-
fn try_fold_optional_chain(
103-
chain_expr: &ChainExpression<'a>,
104-
ctx: &mut Ctx<'a, '_>,
105-
) -> Option<Expression<'a>> {
106-
let left_expr = match &chain_expr.expression {
95+
fn try_fold_optional_chain(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
96+
let Expression::ChainExpression(e) = expr else { return };
97+
let left_expr = match &e.expression {
10798
match_member_expression!(ChainElement) => {
108-
let member_expr = chain_expr.expression.to_member_expression();
99+
let member_expr = e.expression.to_member_expression();
109100
if !member_expr.optional() {
110-
return None;
101+
return;
111102
}
112103
member_expr.object()
113104
}
114105
ChainElement::CallExpression(call_expr) => {
115106
if !call_expr.optional {
116-
return None;
107+
return;
117108
}
118109
&call_expr.callee
119110
}
120-
ChainElement::TSNonNullExpression(_) => return None,
111+
ChainElement::TSNonNullExpression(_) => return,
121112
};
122113
let ty = left_expr.value_type(ctx);
123-
(ty.is_null() || ty.is_undefined())
124-
.then(|| ctx.value_to_expr(chain_expr.span, ConstantValue::Undefined))
114+
if let Some(changed) = (ty.is_null() || ty.is_undefined())
115+
.then(|| ctx.value_to_expr(e.span, ConstantValue::Undefined))
116+
{
117+
*expr = changed;
118+
ctx.state.changed = true;
119+
}
125120
}
126121

127122
/// Try to fold a AND / OR node.
@@ -278,16 +273,14 @@ impl<'a> PeepholeOptimizations {
278273
}
279274

280275
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
281-
fn try_fold_binary_expr(
282-
e: &mut BinaryExpression<'a>,
283-
ctx: &mut Ctx<'a, '_>,
284-
) -> Option<Expression<'a>> {
276+
fn try_fold_binary_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
277+
let Expression::BinaryExpression(e) = expr else { return };
285278
// TODO: tryReduceOperandsForOp
286279

287280
// https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L1136
288281
// https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L1222
289282
let span = e.span;
290-
match e.operator {
283+
let changed = match e.operator {
291284
BinaryOperator::Equality
292285
| BinaryOperator::Inequality
293286
| BinaryOperator::StrictEquality
@@ -339,30 +332,30 @@ impl<'a> PeepholeOptimizations {
339332
.filter(|(_, right)| *right == 0.0 || right.is_nan() || right.is_infinite())
340333
.and_then(|_| ctx.eval_binary(e)),
341334
BinaryOperator::ShiftLeft => {
342-
if let Some((left, right)) = Self::extract_numeric_values(e) {
335+
Self::extract_numeric_values(e).and_then(|(left, right)| {
343336
let result = e.evaluate_value(ctx)?.into_number()?;
344337
let left_len = Self::approximate_printed_int_char_count(left);
345338
let right_len = Self::approximate_printed_int_char_count(right);
346339
let result_len = Self::approximate_printed_int_char_count(result);
347-
if result_len <= left_len + 2 + right_len {
348-
return Some(ctx.value_to_expr(span, ConstantValue::Number(result)));
349-
}
350-
}
351-
None
340+
(result_len <= left_len + 2 + right_len)
341+
.then(|| ctx.value_to_expr(span, ConstantValue::Number(result)))
342+
})
352343
}
353344
BinaryOperator::ShiftRightZeroFill => {
354-
if let Some((left, right)) = Self::extract_numeric_values(e) {
345+
Self::extract_numeric_values(e).and_then(|(left, right)| {
355346
let result = e.evaluate_value(ctx)?.into_number()?;
356347
let left_len = Self::approximate_printed_int_char_count(left);
357348
let right_len = Self::approximate_printed_int_char_count(right);
358349
let result_len = Self::approximate_printed_int_char_count(result);
359-
if result_len <= left_len + 3 + right_len {
360-
return Some(ctx.value_to_expr(span, ConstantValue::Number(result)));
361-
}
362-
}
363-
None
350+
(result_len <= left_len + 3 + right_len)
351+
.then(|| ctx.value_to_expr(span, ConstantValue::Number(result)))
352+
})
364353
}
365354
BinaryOperator::In => None,
355+
};
356+
if let Some(changed) = changed {
357+
*expr = changed;
358+
ctx.state.changed = true;
366359
}
367360
}
368361

@@ -544,21 +537,15 @@ impl<'a> PeepholeOptimizations {
544537
))
545538
}
546539

547-
fn try_fold_number_constructor(
548-
e: &CallExpression<'a>,
549-
ctx: &mut Ctx<'a, '_>,
550-
) -> Option<Expression<'a>> {
551-
let Expression::Identifier(ident) = &e.callee else { return None };
552-
if ident.name != "Number" {
553-
return None;
554-
}
555-
if !ctx.is_global_reference(ident) {
556-
return None;
540+
fn try_fold_number_constructor(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
541+
let Expression::CallExpression(e) = expr else { return };
542+
if !ctx.is_global_expr("Number", &e.callee) {
543+
return;
557544
}
558545
if e.arguments.len() != 1 {
559-
return None;
546+
return;
560547
}
561-
let arg = e.arguments[0].as_expression()?;
548+
let Some(arg) = e.arguments[0].as_expression() else { return };
562549
let value = ConstantValue::Number(match arg {
563550
// `Number(undefined)` -> `NaN`
564551
Expression::Identifier(ident) if ctx.is_identifier_undefined(ident) => f64::NAN,
@@ -574,27 +561,28 @@ impl<'a> PeepholeOptimizations {
574561
if let Some(n) = arg.evaluate_value_to_number(ctx) {
575562
n
576563
} else {
577-
return Some(ctx.ast.expression_unary(
564+
*expr = ctx.ast.expression_unary(
578565
e.span,
579566
UnaryOperator::UnaryPlus,
580567
ctx.ast.expression_string_literal(n.span, n.value, n.raw),
581-
));
568+
);
569+
ctx.state.changed = true;
570+
return;
582571
}
583572
}
584573
e if e.is_void_0() => f64::NAN,
585-
_ => return None,
574+
_ => return,
586575
});
587-
Some(ctx.value_to_expr(e.span, value))
576+
*expr = ctx.value_to_expr(e.span, value);
577+
ctx.state.changed = true;
588578
}
589579

590-
fn try_fold_binary_typeof_comparison(
591-
bin_expr: &BinaryExpression<'a>,
592-
ctx: &mut Ctx<'a, '_>,
593-
) -> Option<Expression<'a>> {
580+
fn try_fold_binary_typeof_comparison(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
581+
let Expression::BinaryExpression(e) = expr else { return };
594582
// `typeof a == typeof a` -> `true`, `typeof a != typeof a` -> `false`
595-
if bin_expr.operator.is_equality() {
583+
if e.operator.is_equality() {
596584
if let (Expression::UnaryExpression(left), Expression::UnaryExpression(right)) =
597-
(&bin_expr.left, &bin_expr.right)
585+
(&e.left, &e.right)
598586
{
599587
if left.operator.is_typeof() && right.operator.is_typeof() {
600588
if let (
@@ -603,13 +591,13 @@ impl<'a> PeepholeOptimizations {
603591
) = (&left.argument, &right.argument)
604592
{
605593
if left_ident.name == right_ident.name {
606-
return Some(ctx.ast.expression_boolean_literal(
607-
bin_expr.span,
608-
matches!(
609-
bin_expr.operator,
610-
BinaryOperator::StrictEquality | BinaryOperator::Equality
611-
),
612-
));
594+
let b = matches!(
595+
e.operator,
596+
BinaryOperator::StrictEquality | BinaryOperator::Equality
597+
);
598+
*expr = ctx.ast.expression_boolean_literal(e.span, b);
599+
ctx.state.changed = true;
600+
return;
613601
}
614602
}
615603
}
@@ -618,18 +606,20 @@ impl<'a> PeepholeOptimizations {
618606

619607
// `typeof a === 'asd` -> `false``
620608
// `typeof a !== 'b'` -> `true``
621-
if let Expression::UnaryExpression(left) = &bin_expr.left {
622-
if left.operator.is_typeof() && bin_expr.operator.is_equality() {
623-
let right_ty = bin_expr.right.value_type(ctx);
609+
if let Expression::UnaryExpression(left) = &e.left {
610+
if left.operator.is_typeof() && e.operator.is_equality() {
611+
let right_ty = e.right.value_type(ctx);
624612

625613
if !right_ty.is_undetermined() && right_ty != ValueType::String {
626-
return Some(ctx.ast.expression_boolean_literal(
627-
bin_expr.span,
628-
bin_expr.operator == BinaryOperator::Inequality
629-
|| bin_expr.operator == BinaryOperator::StrictInequality,
630-
));
614+
*expr = ctx.ast.expression_boolean_literal(
615+
e.span,
616+
e.operator == BinaryOperator::Inequality
617+
|| e.operator == BinaryOperator::StrictInequality,
618+
);
619+
ctx.state.changed = true;
620+
return;
631621
}
632-
if let Expression::StringLiteral(string_lit) = &bin_expr.right {
622+
if let Expression::StringLiteral(string_lit) = &e.right {
633623
if !matches!(
634624
string_lit.value.as_str(),
635625
"string"
@@ -642,17 +632,16 @@ impl<'a> PeepholeOptimizations {
642632
| "function"
643633
| "unknown" // IE
644634
) {
645-
return Some(ctx.ast.expression_boolean_literal(
646-
bin_expr.span,
647-
bin_expr.operator == BinaryOperator::Inequality
648-
|| bin_expr.operator == BinaryOperator::StrictInequality,
649-
));
635+
*expr = ctx.ast.expression_boolean_literal(
636+
e.span,
637+
e.operator == BinaryOperator::Inequality
638+
|| e.operator == BinaryOperator::StrictInequality,
639+
);
640+
ctx.state.changed = true;
650641
}
651642
}
652643
}
653644
}
654-
655-
None
656645
}
657646

658647
fn fold_object_spread(&self, e: &mut ObjectExpression<'a>, ctx: &mut Ctx<'a, '_>) {

0 commit comments

Comments
 (0)