diff --git a/crates/oxc_minifier/src/peephole/minimize_conditions.rs b/crates/oxc_minifier/src/peephole/minimize_conditions.rs index 8ec29eae5f0c9..fab25c7cf8584 100644 --- a/crates/oxc_minifier/src/peephole/minimize_conditions.rs +++ b/crates/oxc_minifier/src/peephole/minimize_conditions.rs @@ -20,46 +20,26 @@ impl<'a> PeepholeOptimizations { expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>, ) { - let mut changed = false; - loop { - let mut local_change = false; - if let Some(folded_expr) = match expr { - Expression::UnaryExpression(e) => self.try_minimize_not(e, ctx), - Expression::BinaryExpression(e) => { - if Self::try_compress_is_loose_boolean(e, ctx) { - local_change = true; - } - Self::try_minimize_binary(e, ctx) - } - Expression::LogicalExpression(e) => self.minimize_logical_expression(e, ctx), - Expression::ConditionalExpression(logical_expr) => { - if self.try_fold_expr_in_boolean_context(&mut logical_expr.test, ctx) { - local_change = true; - } - self.try_minimize_conditional(logical_expr, ctx) - } - Expression::AssignmentExpression(e) => { - if self.try_compress_normal_assignment_to_combined_logical_assignment(e, ctx) { - local_change = true; - } - if Self::try_compress_normal_assignment_to_combined_assignment(e, ctx) { - local_change = true; - } - Self::try_compress_assignment_to_update_expression(e, ctx) + match expr { + Expression::UnaryExpression(_) => self.try_minimize_not(expr, ctx), + Expression::BinaryExpression(e) => { + Self::try_compress_is_loose_boolean(e, ctx); + Self::try_minimize_binary(expr, ctx); + } + Expression::LogicalExpression(_) => self.minimize_logical_expression(expr, ctx), + Expression::ConditionalExpression(logical_expr) => { + self.try_fold_expr_in_boolean_context(&mut logical_expr.test, ctx); + if let Some(changed) = self.try_minimize_conditional(logical_expr, ctx) { + *expr = changed; + ctx.state.changed = true; } - _ => None, - } { - *expr = folded_expr; - local_change = true; } - if local_change { - changed = true; - } else { - break; + Expression::AssignmentExpression(e) => { + self.try_compress_normal_assignment_to_combined_logical_assignment(e, ctx); + Self::try_compress_normal_assignment_to_combined_assignment(e, ctx); + Self::try_compress_assignment_to_update_expression(expr, ctx); } - } - if changed { - ctx.state.changed = true; + _ => {} } } @@ -103,9 +83,9 @@ impl<'a> PeepholeOptimizations { } // "a op b" => "a op b" // "(a op b) op c" => "(a op b) op c" - let mut logic_expr = ctx.ast.logical_expression(span, a, op, b); - self.minimize_logical_expression(&mut logic_expr, ctx) - .unwrap_or_else(|| Expression::LogicalExpression(ctx.ast.alloc(logic_expr))) + let mut logic_expr = ctx.ast.expression_logical(span, a, op, b); + self.minimize_logical_expression(&mut logic_expr, ctx); + logic_expr } // `typeof foo === 'number'` -> `typeof foo == 'number'` @@ -115,17 +95,15 @@ impl<'a> PeepholeOptimizations { // ^^^^^^^^^^^^^^ `ctx.expression_value_type(&e.left).is_boolean()` is `true`. // `x >> +y !== 0` -> `x >> +y` // ^^^^^^^ ctx.expression_value_type(&e.left).is_number()` is `true`. - fn try_minimize_binary( - e: &mut BinaryExpression<'a>, - ctx: &mut Ctx<'a, '_>, - ) -> Option> { + fn try_minimize_binary(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) { + let Expression::BinaryExpression(e) = expr else { return }; if !e.operator.is_equality() { - return None; + return; } let left = e.left.value_type(ctx); let right = e.right.value_type(ctx); if left.is_undetermined() || right.is_undetermined() { - return None; + return; } if left == right { match e.operator { @@ -139,12 +117,14 @@ impl<'a> PeepholeOptimizations { } } if !left.is_boolean() { - return None; + return; } if e.right.may_have_side_effects(ctx) { - return None; + return; } - let mut b = e.right.evaluate_value(ctx).and_then(ConstantValue::into_boolean)?; + let Some(mut b) = e.right.evaluate_value(ctx).and_then(ConstantValue::into_boolean) else { + return; + }; match e.operator { BinaryOperator::Inequality | BinaryOperator::StrictInequality => { e.operator = BinaryOperator::Equality; @@ -154,14 +134,15 @@ impl<'a> PeepholeOptimizations { e.operator = BinaryOperator::Equality; } BinaryOperator::Equality => {} - _ => return None, + _ => return, } - Some(if b { + *expr = if b { e.left.take_in(ctx.ast) } else { let argument = e.left.take_in(ctx.ast); ctx.ast.expression_unary(e.span, UnaryOperator::LogicalNot, argument) - }) + }; + ctx.state.changed = true; } /// Compress `foo == true` into `foo == 1`. @@ -171,11 +152,10 @@ impl<'a> PeepholeOptimizations { /// /// In `IsLooselyEqual`, `true` and `false` are converted to `1` and `0` first. /// - fn try_compress_is_loose_boolean(e: &mut BinaryExpression<'a>, ctx: &mut Ctx<'a, '_>) -> bool { + fn try_compress_is_loose_boolean(e: &mut BinaryExpression<'a>, ctx: &mut Ctx<'a, '_>) { if !matches!(e.operator, BinaryOperator::Equality | BinaryOperator::Inequality) { - return false; + return; } - if let Some(ConstantValue::Boolean(left_bool)) = e.left.evaluate_value(ctx) { e.left = ctx.ast.expression_numeric_literal( e.left.span(), @@ -183,7 +163,8 @@ impl<'a> PeepholeOptimizations { None, NumberBase::Decimal, ); - return true; + ctx.state.changed = true; + return; } if let Some(ConstantValue::Boolean(right_bool)) = e.right.evaluate_value(ctx) { e.right = ctx.ast.expression_numeric_literal( @@ -192,9 +173,8 @@ impl<'a> PeepholeOptimizations { None, NumberBase::Decimal, ); - return true; + ctx.state.changed = true; } - false } /// Returns the identifier or the assignment target's identifier of the given expression. @@ -222,15 +202,15 @@ impl<'a> PeepholeOptimizations { &self, expr: &mut AssignmentExpression<'a>, ctx: &mut Ctx<'a, '_>, - ) -> bool { + ) { if ctx.options().target < ESTarget::ES2020 { - return false; + return; } if !matches!(expr.operator, AssignmentOperator::Assign) { - return false; + return; } - let Expression::LogicalExpression(logical_expr) = &mut expr.right else { return false }; + let Expression::LogicalExpression(logical_expr) = &mut expr.right else { return }; // NOTE: if the right hand side is an anonymous function, applying this compression will // set the `name` property of that function. // Since codes relying on the fact that function's name is undefined should be rare, @@ -241,64 +221,64 @@ impl<'a> PeepholeOptimizations { Expression::Identifier(read_id_ref), ) = (&expr.left, &logical_expr.left) else { - return false; + return; }; // It should also early return when the reference might refer to a reference value created by a with statement // when the minifier supports with statements if write_id_ref.name != read_id_ref.name || ctx.is_global_reference(write_id_ref) { - return false; + return; } let new_op = logical_expr.operator.to_assignment_operator(); expr.operator = new_op; expr.right = logical_expr.right.take_in(ctx.ast); - true + ctx.state.changed = true; } /// Compress `a = a + b` to `a += b` fn try_compress_normal_assignment_to_combined_assignment( expr: &mut AssignmentExpression<'a>, ctx: &mut Ctx<'a, '_>, - ) -> bool { + ) { if !matches!(expr.operator, AssignmentOperator::Assign) { - return false; + return; } - - let Expression::BinaryExpression(binary_expr) = &mut expr.right else { return false }; - let Some(new_op) = binary_expr.operator.to_assignment_operator() else { return false }; - + let Expression::BinaryExpression(binary_expr) = &mut expr.right else { return }; + let Some(new_op) = binary_expr.operator.to_assignment_operator() else { return }; if !Self::has_no_side_effect_for_evaluation_same_target(&expr.left, &binary_expr.left, ctx) { - return false; + return; } - expr.operator = new_op; expr.right = binary_expr.right.take_in(ctx.ast); - true + ctx.state.changed = true; } /// Compress `a -= 1` to `--a` and `a -= -1` to `++a` #[expect(clippy::float_cmp)] fn try_compress_assignment_to_update_expression( - expr: &mut AssignmentExpression<'a>, + expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>, - ) -> Option> { - if !matches!(expr.operator, AssignmentOperator::Subtraction) { - return None; + ) { + let Expression::AssignmentExpression(e) = expr else { return }; + if !matches!(e.operator, AssignmentOperator::Subtraction) { + return; } - let Expression::NumericLiteral(num) = &expr.right else { - return None; - }; - let target = expr.left.as_simple_assignment_target_mut()?; - let operator = if num.value == 1.0 { - UpdateOperator::Decrement - } else if num.value == -1.0 { - UpdateOperator::Increment + let operator = if let Expression::NumericLiteral(num) = &e.right { + if num.value == 1.0 { + UpdateOperator::Decrement + } else if num.value == -1.0 { + UpdateOperator::Increment + } else { + return; + } } else { - return None; + return; }; + let Some(target) = e.left.as_simple_assignment_target_mut() else { return }; let target = target.take_in(ctx.ast); - Some(ctx.ast.expression_update(expr.span, operator, true, target)) + *expr = ctx.ast.expression_update(e.span, operator, true, target); + ctx.state.changed = true; } } diff --git a/crates/oxc_minifier/src/peephole/minimize_expression_in_boolean_context.rs b/crates/oxc_minifier/src/peephole/minimize_expression_in_boolean_context.rs index 8cf121b81e9f4..8c71201194aab 100644 --- a/crates/oxc_minifier/src/peephole/minimize_expression_in_boolean_context.rs +++ b/crates/oxc_minifier/src/peephole/minimize_expression_in_boolean_context.rs @@ -33,7 +33,7 @@ impl<'a> PeepholeOptimizations { &self, expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>, - ) -> bool { + ) { match expr { // "!!a" => "a" Expression::UnaryExpression(u1) if u1.operator.is_not() => { @@ -42,7 +42,7 @@ impl<'a> PeepholeOptimizations { let mut e = u2.argument.take_in(ctx.ast); self.try_fold_expr_in_boolean_context(&mut e, ctx); *expr = e; - return true; + ctx.state.changed = true; } } } @@ -62,7 +62,7 @@ impl<'a> PeepholeOptimizations { // `if ((a | b) === 0);", "if (!(a | b));")` ctx.ast.expression_unary(e.span, UnaryOperator::LogicalNot, argument) }; - return true; + ctx.state.changed = true; } // "if (!!a && !!b)" => "if (a && b)" Expression::LogicalExpression(e) if e.operator.is_and() => { @@ -71,7 +71,7 @@ impl<'a> PeepholeOptimizations { // "if (anything && truthyNoSideEffects)" => "if (anything)" if e.right.get_side_free_boolean_value(ctx) == Some(true) { *expr = e.left.take_in(ctx.ast); - return true; + ctx.state.changed = true; } } // "if (!!a ||!!b)" => "if (a || b)" @@ -81,7 +81,7 @@ impl<'a> PeepholeOptimizations { // "if (anything || falsyNoSideEffects)" => "if (anything)" if e.right.get_side_free_boolean_value(ctx) == Some(false) { *expr = e.left.take_in(ctx.ast); - return true; + ctx.state.changed = true; } } Expression::ConditionalExpression(e) => { @@ -100,7 +100,8 @@ impl<'a> PeepholeOptimizations { (LogicalOperator::And, self.minimize_not(left.span(), left, ctx)) }; *expr = self.join_with_left_associative_op(span, op, left, right, ctx); - return true; + ctx.state.changed = true; + return; } if let Some(boolean) = e.alternate.get_side_free_boolean_value(ctx) { let left = e.test.take_in(ctx.ast); @@ -114,17 +115,16 @@ impl<'a> PeepholeOptimizations { (LogicalOperator::And, left) }; *expr = self.join_with_left_associative_op(span, op, left, right, ctx); - return true; + ctx.state.changed = true; } } Expression::SequenceExpression(seq_expr) => { if let Some(last) = seq_expr.expressions.last_mut() { - return self.try_fold_expr_in_boolean_context(last, ctx); + self.try_fold_expr_in_boolean_context(last, ctx); } } _ => {} } - false } } diff --git a/crates/oxc_minifier/src/peephole/minimize_logical_expression.rs b/crates/oxc_minifier/src/peephole/minimize_logical_expression.rs index 5539d407c47dd..d9c629d6ce518 100644 --- a/crates/oxc_minifier/src/peephole/minimize_logical_expression.rs +++ b/crates/oxc_minifier/src/peephole/minimize_logical_expression.rs @@ -8,13 +8,13 @@ use crate::ctx::Ctx; use super::PeepholeOptimizations; impl<'a> PeepholeOptimizations { - pub fn minimize_logical_expression( - &self, - e: &mut LogicalExpression<'a>, - ctx: &mut Ctx<'a, '_>, - ) -> Option> { - Self::try_compress_is_null_or_undefined(e, ctx) - .or_else(|| self.try_compress_logical_expression_to_assignment_expression(e, ctx)) + pub fn minimize_logical_expression(&self, expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) { + let Expression::LogicalExpression(e) = expr else { return }; + if let Some(changed) = Self::try_compress_is_null_or_undefined(e, ctx) { + *expr = changed; + ctx.state.changed = true; + } + Self::try_compress_logical_expression_to_assignment_expression(expr, ctx); } /// Compress `foo === null || foo === undefined` into `foo == null`. @@ -205,32 +205,31 @@ impl<'a> PeepholeOptimizations { /// /// Also `a || (foo, bar, a = b)` to `a ||= (foo, bar, b)` fn try_compress_logical_expression_to_assignment_expression( - &self, - expr: &mut LogicalExpression<'a>, + expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>, - ) -> Option> { + ) { if ctx.options().target < ESTarget::ES2020 { - return None; + return; } - - if let Expression::SequenceExpression(sequence_expr) = &mut expr.right { + let Expression::LogicalExpression(e) = expr else { return }; + if let Expression::SequenceExpression(sequence_expr) = &e.right { let Some(Expression::AssignmentExpression(assignment_expr)) = sequence_expr.expressions.last() else { - return None; + return; }; if assignment_expr.operator != AssignmentOperator::Assign { - return None; + return; } - if !Self::has_no_side_effect_for_evaluation_same_target( &assignment_expr.left, - &expr.left, + &e.left, ctx, ) { - return None; + return; } + let Expression::SequenceExpression(sequence_expr) = &mut e.right else { return }; let Some(Expression::AssignmentExpression(mut assignment_expr)) = sequence_expr.expressions.pop() else { @@ -239,30 +238,34 @@ impl<'a> PeepholeOptimizations { let assign_value = assignment_expr.right.take_in(ctx.ast); sequence_expr.expressions.push(assign_value); - return Some(ctx.ast.expression_assignment( - expr.span, - expr.operator.to_assignment_operator(), + *expr = ctx.ast.expression_assignment( + e.span, + e.operator.to_assignment_operator(), assignment_expr.left.take_in(ctx.ast), - expr.right.take_in(ctx.ast), - )); + e.right.take_in(ctx.ast), + ); + ctx.state.changed = true; + return; } - let Expression::AssignmentExpression(assignment_expr) = &mut expr.right else { - return None; + let Expression::AssignmentExpression(assignment_expr) = &e.right else { + return; }; if assignment_expr.operator != AssignmentOperator::Assign { - return None; + return; } - let new_op = expr.operator.to_assignment_operator(); - if !Self::has_no_side_effect_for_evaluation_same_target( - &assignment_expr.left, - &expr.left, - ctx, - ) { - return None; + let new_op = e.operator.to_assignment_operator(); + if !Self::has_no_side_effect_for_evaluation_same_target(&assignment_expr.left, &e.left, ctx) + { + return; } - assignment_expr.span = expr.span; + let span = e.span; + let Expression::AssignmentExpression(assignment_expr) = &mut e.right else { + return; + }; + assignment_expr.span = span; assignment_expr.operator = new_op; - Some(expr.right.take_in(ctx.ast)) + *expr = e.right.take_in(ctx.ast); + ctx.state.changed = true; } } diff --git a/crates/oxc_minifier/src/peephole/minimize_not_expression.rs b/crates/oxc_minifier/src/peephole/minimize_not_expression.rs index 29d1679dad9de..1f87f0fbcb923 100644 --- a/crates/oxc_minifier/src/peephole/minimize_not_expression.rs +++ b/crates/oxc_minifier/src/peephole/minimize_not_expression.rs @@ -14,47 +14,46 @@ impl<'a> PeepholeOptimizations { expr: Expression<'a>, ctx: &mut Ctx<'a, '_>, ) -> Expression<'a> { - let mut unary = ctx.ast.unary_expression(span, UnaryOperator::LogicalNot, expr); - self.try_minimize_not(&mut unary, ctx) - .unwrap_or_else(|| Expression::UnaryExpression(ctx.ast.alloc(unary))) + let mut unary = ctx.ast.expression_unary(span, UnaryOperator::LogicalNot, expr); + self.try_minimize_not(&mut unary, ctx); + unary } /// `MaybeSimplifyNot`: - pub fn try_minimize_not( - &self, - expr: &mut UnaryExpression<'a>, - ctx: &mut Ctx<'a, '_>, - ) -> Option> { - if !expr.operator.is_not() { - return None; + pub fn try_minimize_not(&self, expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) { + let Expression::UnaryExpression(e) = expr else { return }; + if !e.operator.is_not() { + return; } - self.try_fold_expr_in_boolean_context(&mut expr.argument, ctx); - match &mut expr.argument { + self.try_fold_expr_in_boolean_context(&mut e.argument, ctx); + match &mut e.argument { // `!!true` -> `true` // `!!false` -> `false` Expression::UnaryExpression(e) if e.operator.is_not() && e.argument.value_type(ctx).is_boolean() => { - Some(e.argument.take_in(ctx.ast)) + *expr = e.argument.take_in(ctx.ast); + ctx.state.changed = true; } // `!(a == b)` => `a != b` // `!(a != b)` => `a == b` // `!(a === b)` => `a !== b` // `!(a !== b)` => `a === b` - Expression::BinaryExpression(e) if e.operator.is_equality() => { - e.operator = e.operator.equality_inverse_operator().unwrap(); - Some(expr.argument.take_in(ctx.ast)) + Expression::BinaryExpression(binary_expr) if binary_expr.operator.is_equality() => { + binary_expr.operator = binary_expr.operator.equality_inverse_operator().unwrap(); + *expr = e.argument.take_in(ctx.ast); + ctx.state.changed = true; } // "!(a, b)" => "a, !b" Expression::SequenceExpression(sequence_expr) => { if let Some(last_expr) = sequence_expr.expressions.last_mut() { *last_expr = self.minimize_not(last_expr.span(), last_expr.take_in(ctx.ast), ctx); - return Some(expr.argument.take_in(ctx.ast)); + *expr = e.argument.take_in(ctx.ast); + ctx.state.changed = true; } - None } - _ => None, + _ => {} } } } diff --git a/crates/oxc_minifier/src/peephole/mod.rs b/crates/oxc_minifier/src/peephole/mod.rs index a95e44d97402b..5704af4f54e0c 100644 --- a/crates/oxc_minifier/src/peephole/mod.rs +++ b/crates/oxc_minifier/src/peephole/mod.rs @@ -182,11 +182,9 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations { } fn exit_unary_expression(&mut self, expr: &mut UnaryExpression<'a>, ctx: &mut TraverseCtx<'a>) { - let mut ctx = Ctx::new(ctx); - if expr.operator.is_not() - && self.try_fold_expr_in_boolean_context(&mut expr.argument, &mut ctx) - { - ctx.state.changed = true; + if expr.operator.is_not() { + let mut ctx = Ctx::new(ctx); + self.try_fold_expr_in_boolean_context(&mut expr.argument, &mut ctx); } } diff --git a/crates/oxc_minifier/src/peephole/remove_unused_expression.rs b/crates/oxc_minifier/src/peephole/remove_unused_expression.rs index b4160c2eeb262..bd936e409e73d 100644 --- a/crates/oxc_minifier/src/peephole/remove_unused_expression.rs +++ b/crates/oxc_minifier/src/peephole/remove_unused_expression.rs @@ -68,10 +68,8 @@ impl<'a> PeepholeOptimizations { fn fold_logical_expression(&self, e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool { let Expression::LogicalExpression(logical_expr) = e else { return false }; - if !logical_expr.operator.is_coalesce() - && self.try_fold_expr_in_boolean_context(&mut logical_expr.left, ctx) - { - ctx.state.changed = true; + if !logical_expr.operator.is_coalesce() { + self.try_fold_expr_in_boolean_context(&mut logical_expr.left, ctx); } if self.remove_unused_expression(&mut logical_expr.right, ctx) { self.remove_unused_expression(&mut logical_expr.left, ctx);