diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index d0bc7540f5134..0d84b82ce2754 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -38,70 +38,100 @@ impl<'a> CompressorPass<'a> for PeepholeFoldConstants { impl<'a> Traverse<'a> for PeepholeFoldConstants { fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - self.fold_expression(expr, ctx); - } -} - -impl<'a> PeepholeFoldConstants { - pub fn new() -> Self { - Self { changed: false } - } - - // [optimizeSubtree](https://github.com/google/closure-compiler/blob/75335a5138dde05030747abfd3c852cd34ea7429/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L72) - // TODO: tryReduceOperandsForOp - pub fn fold_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { if let Some(folded_expr) = match expr { - Expression::BinaryExpression(e) => self.try_fold_binary_operator(e, ctx), - Expression::LogicalExpression(e) - if matches!(e.operator, LogicalOperator::And | LogicalOperator::Or) => - { - self.try_fold_and_or(e, ctx) - } - Expression::LogicalExpression(e) if e.operator == LogicalOperator::Coalesce => { - self.try_fold_coalesce(e, ctx) + Expression::CallExpression(e) => { + self.try_fold_useless_object_dot_define_properties_call(e, ctx) } + Expression::NewExpression(e) => self.try_fold_ctor_cal(e, ctx), + // TODO + // return tryFoldSpread(subtree); + Expression::ArrayExpression(e) => self.try_flatten_array_expression(e, ctx), + Expression::ObjectExpression(e) => self.try_flatten_object_expression(e, ctx), + Expression::BinaryExpression(e) => self.try_fold_binary_expression(e, ctx), Expression::UnaryExpression(e) => self.try_fold_unary_expression(e, ctx), + // TODO: return tryFoldGetProp(subtree); + Expression::LogicalExpression(e) => self.try_fold_logical_expression(e, ctx), + // TODO: tryFoldGetElem + // TODO: tryFoldAssign _ => None, } { *expr = folded_expr; self.changed = true; }; } +} + +impl<'a> PeepholeFoldConstants { + pub fn new() -> Self { + Self { changed: false } + } + + fn try_fold_useless_object_dot_define_properties_call( + &mut self, + _call_expr: &mut CallExpression<'a>, + _ctx: &mut TraverseCtx<'a>, + ) -> Option> { + None + } - fn try_fold_binary_operator( + fn try_fold_ctor_cal( + &mut self, + _new_expr: &mut NewExpression<'a>, + _ctx: &mut TraverseCtx<'a>, + ) -> Option> { + None + } + + /// Folds 'typeof(foo)' if foo is a literal, e.g. + /// `typeof("bar") --> "string"` + /// `typeof(6) --> "number"` + fn try_fold_type_of( &self, - binary_expr: &mut BinaryExpression<'a>, + expr: &mut UnaryExpression<'a>, ctx: &mut TraverseCtx<'a>, ) -> Option> { - match binary_expr.operator { - BinaryOperator::Equality - | BinaryOperator::Inequality - | BinaryOperator::StrictEquality - | BinaryOperator::StrictInequality - | BinaryOperator::LessThan - | BinaryOperator::LessEqualThan - | BinaryOperator::GreaterThan - | BinaryOperator::GreaterEqualThan => self.try_fold_comparison( - binary_expr.span, - binary_expr.operator, - &binary_expr.left, - &binary_expr.right, - ctx, - ), - BinaryOperator::ShiftLeft - | BinaryOperator::ShiftRight - | BinaryOperator::ShiftRightZeroFill => self.try_fold_shift( - binary_expr.span, - binary_expr.operator, - &binary_expr.left, - &binary_expr.right, - ctx, - ), - BinaryOperator::Addition => { - self.try_fold_addition(binary_expr.span, &binary_expr.left, &binary_expr.right, ctx) - } - _ => None, + if !expr.argument.is_literal_value(/* include_function */ true) { + return None; } + let s = match &mut expr.argument { + Expression::FunctionExpression(_) => "function", + Expression::StringLiteral(_) => "string", + Expression::NumericLiteral(_) => "number", + Expression::BooleanLiteral(_) => "boolean", + Expression::NullLiteral(_) + | Expression::ObjectExpression(_) + | Expression::ArrayExpression(_) => "object", + Expression::UnaryExpression(e) if e.operator == UnaryOperator::Void => "undefined", + Expression::BigIntLiteral(_) => "bigint", + Expression::Identifier(ident) if ctx.is_identifier_undefined(ident) => "undefined", + _ => return None, + }; + Some(ctx.ast.expression_string_literal(SPAN, s)) + } + + // TODO + // fn try_fold_spread( + // &mut self, + // _new_expr: &mut NewExpression<'a>, + // _ctx: &mut TraverseCtx<'a>, + // ) -> Option> { + // None + // } + + fn try_flatten_array_expression( + &mut self, + _new_expr: &mut ArrayExpression<'a>, + _ctx: &mut TraverseCtx<'a>, + ) -> Option> { + None + } + + fn try_flatten_object_expression( + &mut self, + _new_expr: &mut ObjectExpression<'a>, + _ctx: &mut TraverseCtx<'a>, + ) -> Option> { + None } fn try_fold_unary_expression( @@ -112,6 +142,7 @@ impl<'a> PeepholeFoldConstants { match expr.operator { UnaryOperator::Void => self.try_reduce_void(expr, ctx), UnaryOperator::Typeof => self.try_fold_type_of(expr, ctx), + // TODO: tryReduceOperandsForOp #[allow(clippy::float_cmp)] UnaryOperator::LogicalNot => { if let Expression::NumericLiteral(n) = &expr.argument { @@ -148,31 +179,167 @@ impl<'a> PeepholeFoldConstants { None } - /// Folds 'typeof(foo)' if foo is a literal, e.g. - /// `typeof("bar") --> "string"` - /// `typeof(6) --> "number"` - fn try_fold_type_of( + fn try_fold_logical_expression( &self, - expr: &mut UnaryExpression<'a>, + logical_expr: &mut LogicalExpression<'a>, ctx: &mut TraverseCtx<'a>, ) -> Option> { - if !expr.argument.is_literal_value(/* include_function */ true) { - return None; + match logical_expr.operator { + LogicalOperator::And | LogicalOperator::Or => self.try_fold_and_or(logical_expr, ctx), + LogicalOperator::Coalesce => self.try_fold_coalesce(logical_expr, ctx), + } + } + + /// Try to fold a AND / OR node. + /// + /// port from [closure-compiler](https://github.com/google/closure-compiler/blob/09094b551915a6487a980a783831cba58b5739d1/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L587) + pub fn try_fold_and_or( + &self, + logical_expr: &mut LogicalExpression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + let op = logical_expr.operator; + debug_assert!(matches!(op, LogicalOperator::And | LogicalOperator::Or)); + + let left = &logical_expr.left; + let left_val = ctx.get_boolean_value(left).to_option(); + + if let Some(lval) = left_val { + // Bail `0 && (module.exports = {})` for `cjs-module-lexer`. + if !lval { + if let Expression::AssignmentExpression(assign_expr) = &logical_expr.right { + if let Some(member_expr) = assign_expr.left.as_member_expression() { + if member_expr.is_specific_member_access("module", "exports") { + return None; + } + } + } + } + + // (TRUE || x) => TRUE (also, (3 || x) => 3) + // (FALSE && x) => FALSE + if if lval { op == LogicalOperator::Or } else { op == LogicalOperator::And } { + return Some(ctx.ast.move_expression(&mut logical_expr.left)); + } else if !left.may_have_side_effects() { + let parent = ctx.ancestry.parent(); + // Bail `let o = { f() { assert.ok(this !== o); } }; (true && o.f)(); (true && o.f)``;` + if parent.is_tagged_template_expression() + || matches!(parent, Ancestor::CallExpressionCallee(_)) + { + return None; + } + // (FALSE || x) => x + // (TRUE && x) => x + return Some(ctx.ast.move_expression(&mut logical_expr.right)); + } + // Left side may have side effects, but we know its boolean value. + // e.g. true_with_sideeffects || foo() => true_with_sideeffects, foo() + // or: false_with_sideeffects && foo() => false_with_sideeffects, foo() + let left = ctx.ast.move_expression(&mut logical_expr.left); + let right = ctx.ast.move_expression(&mut logical_expr.right); + let mut vec = ctx.ast.vec_with_capacity(2); + vec.push(left); + vec.push(right); + let sequence_expr = ctx.ast.expression_sequence(logical_expr.span, vec); + return Some(sequence_expr); + } else if let Expression::LogicalExpression(left_child) = &mut logical_expr.left { + if left_child.operator == logical_expr.operator { + let left_child_right_boolean = ctx.get_boolean_value(&left_child.right).to_option(); + let left_child_op = left_child.operator; + if let Some(right_boolean) = left_child_right_boolean { + if !left_child.right.may_have_side_effects() { + // a || false || b => a || b + // a && true && b => a && b + if !right_boolean && left_child_op == LogicalOperator::Or + || right_boolean && left_child_op == LogicalOperator::And + { + let left = ctx.ast.move_expression(&mut left_child.left); + let right = ctx.ast.move_expression(&mut logical_expr.right); + let logic_expr = ctx.ast.expression_logical( + logical_expr.span, + left, + left_child_op, + right, + ); + return Some(logic_expr); + } + } + } + } + } + None + } + + /// Try to fold a nullish coalesce `foo ?? bar`. + pub fn try_fold_coalesce( + &self, + logical_expr: &mut LogicalExpression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + debug_assert_eq!(logical_expr.operator, LogicalOperator::Coalesce); + let left = &logical_expr.left; + let left_val = ctx.get_known_value_type(left); + match left_val { + ValueType::Null | ValueType::Void => { + Some(if left.may_have_side_effects() { + // e.g. `(a(), null) ?? 1` => `(a(), null, 1)` + let expressions = ctx.ast.vec_from_iter([ + ctx.ast.move_expression(&mut logical_expr.left), + ctx.ast.move_expression(&mut logical_expr.right), + ]); + ctx.ast.expression_sequence(SPAN, expressions) + } else { + // nullish condition => this expression evaluates to the right side. + ctx.ast.move_expression(&mut logical_expr.right) + }) + } + ValueType::Number + | ValueType::Bigint + | ValueType::String + | ValueType::Boolean + | ValueType::Object => { + // non-nullish condition => this expression evaluates to the left side. + Some(ctx.ast.move_expression(&mut logical_expr.left)) + } + ValueType::Undetermined => None, + } + } + + fn try_fold_binary_expression( + &self, + e: &mut BinaryExpression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + // TODO: tryReduceOperandsForOp + match e.operator { + op if op.is_bitshift() => { + self.try_fold_shift(e.span, e.operator, &e.left, &e.right, ctx) + } + BinaryOperator::Instanceof => self.try_fold_instanceof(e.span, &e.left, &e.right, ctx), + BinaryOperator::Addition => self.try_fold_addition(e.span, &e.left, &e.right, ctx), + BinaryOperator::Subtraction + | BinaryOperator::Division + | BinaryOperator::Remainder + | BinaryOperator::Exponential => { + self.try_fold_arithmetic_op(e.span, &e.left, &e.right, ctx) + } + BinaryOperator::Multiplication + | BinaryOperator::BitwiseAnd + | BinaryOperator::BitwiseOR + | BinaryOperator::BitwiseXOR => { + // TODO: + // self.try_fold_arithmetic_op(e.span, &e.left, &e.right, ctx) + // if (result != subtree) { + // return result; + // } + // return tryFoldLeftChildOp(subtree, left, right); + None + } + op if op.is_equality() || op.is_compare() => { + self.try_fold_comparison(e.span, e.operator, &e.left, &e.right, ctx) + } + _ => None, } - let s = match &mut expr.argument { - Expression::FunctionExpression(_) => "function", - Expression::StringLiteral(_) => "string", - Expression::NumericLiteral(_) => "number", - Expression::BooleanLiteral(_) => "boolean", - Expression::NullLiteral(_) - | Expression::ObjectExpression(_) - | Expression::ArrayExpression(_) => "object", - Expression::UnaryExpression(e) if e.operator == UnaryOperator::Void => "undefined", - Expression::BigIntLiteral(_) => "bigint", - Expression::Identifier(ident) if ctx.is_identifier_undefined(ident) => "undefined", - _ => return None, - }; - Some(ctx.ast.expression_string_literal(SPAN, s)) } fn try_fold_addition<'b>( @@ -219,6 +386,26 @@ impl<'a> PeepholeFoldConstants { } } + fn try_fold_arithmetic_op<'b>( + &self, + _span: Span, + _left: &'b Expression<'a>, + _right: &'b Expression<'a>, + _ctx: &mut TraverseCtx<'a>, + ) -> Option> { + None + } + + fn try_fold_instanceof<'b>( + &self, + _span: Span, + _left: &'b Expression<'a>, + _right: &'b Expression<'a>, + _ctx: &mut TraverseCtx<'a>, + ) -> Option> { + None + } + fn try_fold_comparison<'b>( &self, span: Span, @@ -587,121 +774,6 @@ impl<'a> PeepholeFoldConstants { None } - - /// Try to fold a AND / OR node. - /// - /// port from [closure-compiler](https://github.com/google/closure-compiler/blob/09094b551915a6487a980a783831cba58b5739d1/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L587) - pub fn try_fold_and_or( - &self, - logical_expr: &mut LogicalExpression<'a>, - ctx: &mut TraverseCtx<'a>, - ) -> Option> { - let op = logical_expr.operator; - debug_assert!(matches!(op, LogicalOperator::And | LogicalOperator::Or)); - - let left = &logical_expr.left; - let left_val = ctx.get_boolean_value(left).to_option(); - - if let Some(lval) = left_val { - // Bail `0 && (module.exports = {})` for `cjs-module-lexer`. - if !lval { - if let Expression::AssignmentExpression(assign_expr) = &logical_expr.right { - if let Some(member_expr) = assign_expr.left.as_member_expression() { - if member_expr.is_specific_member_access("module", "exports") { - return None; - } - } - } - } - - // (TRUE || x) => TRUE (also, (3 || x) => 3) - // (FALSE && x) => FALSE - if if lval { op == LogicalOperator::Or } else { op == LogicalOperator::And } { - return Some(ctx.ast.move_expression(&mut logical_expr.left)); - } else if !left.may_have_side_effects() { - let parent = ctx.ancestry.parent(); - // Bail `let o = { f() { assert.ok(this !== o); } }; (true && o.f)(); (true && o.f)``;` - if parent.is_tagged_template_expression() - || matches!(parent, Ancestor::CallExpressionCallee(_)) - { - return None; - } - // (FALSE || x) => x - // (TRUE && x) => x - return Some(ctx.ast.move_expression(&mut logical_expr.right)); - } - // Left side may have side effects, but we know its boolean value. - // e.g. true_with_sideeffects || foo() => true_with_sideeffects, foo() - // or: false_with_sideeffects && foo() => false_with_sideeffects, foo() - let left = ctx.ast.move_expression(&mut logical_expr.left); - let right = ctx.ast.move_expression(&mut logical_expr.right); - let mut vec = ctx.ast.vec_with_capacity(2); - vec.push(left); - vec.push(right); - let sequence_expr = ctx.ast.expression_sequence(logical_expr.span, vec); - return Some(sequence_expr); - } else if let Expression::LogicalExpression(left_child) = &mut logical_expr.left { - if left_child.operator == logical_expr.operator { - let left_child_right_boolean = ctx.get_boolean_value(&left_child.right).to_option(); - let left_child_op = left_child.operator; - if let Some(right_boolean) = left_child_right_boolean { - if !left_child.right.may_have_side_effects() { - // a || false || b => a || b - // a && true && b => a && b - if !right_boolean && left_child_op == LogicalOperator::Or - || right_boolean && left_child_op == LogicalOperator::And - { - let left = ctx.ast.move_expression(&mut left_child.left); - let right = ctx.ast.move_expression(&mut logical_expr.right); - let logic_expr = ctx.ast.expression_logical( - logical_expr.span, - left, - left_child_op, - right, - ); - return Some(logic_expr); - } - } - } - } - } - None - } - - /// Try to fold a nullish coalesce `foo ?? bar`. - pub fn try_fold_coalesce( - &self, - logical_expr: &mut LogicalExpression<'a>, - ctx: &mut TraverseCtx<'a>, - ) -> Option> { - debug_assert_eq!(logical_expr.operator, LogicalOperator::Coalesce); - let left = &logical_expr.left; - let left_val = ctx.get_known_value_type(left); - match left_val { - ValueType::Null | ValueType::Void => { - Some(if left.may_have_side_effects() { - // e.g. `(a(), null) ?? 1` => `(a(), null, 1)` - let expressions = ctx.ast.vec_from_iter([ - ctx.ast.move_expression(&mut logical_expr.left), - ctx.ast.move_expression(&mut logical_expr.right), - ]); - ctx.ast.expression_sequence(SPAN, expressions) - } else { - // nullish condition => this expression evaluates to the right side. - ctx.ast.move_expression(&mut logical_expr.right) - }) - } - ValueType::Number - | ValueType::Bigint - | ValueType::String - | ValueType::Boolean - | ValueType::Object => { - // non-nullish condition => this expression evaluates to the left side. - Some(ctx.ast.move_expression(&mut logical_expr.left)) - } - ValueType::Undetermined => None, - } - } } /// diff --git a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs index fed59e69deaf3..65f225e30cb78 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs @@ -27,7 +27,13 @@ impl<'a> CompressorPass<'a> for PeepholeRemoveDeadCode { impl<'a> Traverse<'a> for PeepholeRemoveDeadCode { fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { - self.fold_if_statement(stmt, ctx); + if let Some(new_stmt) = match stmt { + Statement::IfStatement(if_stmt) => self.try_fold_if(if_stmt, ctx), + _ => None, + } { + *stmt = new_stmt; + self.changed = true; + } } fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { @@ -116,25 +122,30 @@ impl<'a> PeepholeRemoveDeadCode { } } - fn fold_if_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { - let Statement::IfStatement(if_stmt) = stmt else { return }; - + fn try_fold_if( + &mut self, + if_stmt: &mut IfStatement<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { // Descend and remove `else` blocks first. - if let Some(alternate) = &mut if_stmt.alternate { - self.fold_if_statement(alternate, ctx); - if matches!(alternate, Statement::EmptyStatement(_)) { - if_stmt.alternate = None; - self.changed = true; + if let Some(Statement::IfStatement(alternate)) = &mut if_stmt.alternate { + if let Some(new_stmt) = self.try_fold_if(alternate, ctx) { + if matches!(new_stmt, Statement::EmptyStatement(_)) { + if_stmt.alternate = None; + self.changed = true; + } else { + if_stmt.alternate = Some(new_stmt); + } } } match ctx.get_boolean_value(&if_stmt.test) { Tri::True => { - *stmt = ctx.ast.move_statement(&mut if_stmt.consequent); - self.changed = true; + // self.changed = true; + Some(ctx.ast.move_statement(&mut if_stmt.consequent)) } Tri::False => { - *stmt = if let Some(alternate) = &mut if_stmt.alternate { + Some(if let Some(alternate) = &mut if_stmt.alternate { ctx.ast.move_statement(alternate) } else { // Keep hoisted `vars` from the consequent block. @@ -143,10 +154,10 @@ impl<'a> PeepholeRemoveDeadCode { keep_var .get_variable_declaration_statement() .unwrap_or_else(|| ctx.ast.statement_empty(SPAN)) - }; - self.changed = true; + }) + // self.changed = true; } - Tri::Unknown => {} + Tri::Unknown => None, } }