Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(minifier): clean up peephole_substitute_alternate_syntax.rs #8111

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -79,76 +79,33 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {

fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
let ctx = Ctx(ctx);

// Change syntax
match expr {
Expression::AssignmentExpression(assignment_expr) => {
if let Some(new_expr) =
Self::try_compress_assignment_expression(assignment_expr, ctx)
{
*expr = new_expr;
self.changed = true;
}
}
Expression::LogicalExpression(logical_expr) => {
if let Some(new_expr) = Self::try_compress_is_null_or_undefined(logical_expr, ctx) {
*expr = new_expr;
self.changed = true;
}
Expression::ArrowFunctionExpression(arrow_expr) => {
self.try_compress_arrow_expression(arrow_expr, ctx);
}
Expression::ChainExpression(e) => self.try_compress_chain_call_expression(e, ctx),
_ => {}
}
self.try_compress_boolean(expr, ctx);
self.try_compress_undefined(expr, ctx);
match expr {
Expression::NewExpression(new_expr) => {
if let Some(new_expr) = Self::try_fold_new_expression(new_expr, ctx) {
*expr = new_expr;
self.changed = true;
}
}
Expression::CallExpression(call_expr) => {
if let Some(new_expr) =
Self::try_fold_literal_constructor_call_expression(call_expr, ctx)
.or_else(|| Self::try_fold_simple_function_call(call_expr, ctx))
{
*expr = new_expr;
self.changed = true;
}
}
Expression::ChainExpression(chain_expr) => {
if let ChainElement::CallExpression(call_expr) = &mut chain_expr.expression {
self.try_fold_chain_call_expression(call_expr, ctx);
}
}
Expression::TemplateLiteral(_) => {
if let Some(val) = expr.to_js_string() {
*expr = ctx.ast.expression_string_literal(expr.span(), val, None);
self.changed = true;
}
}
// `() => { return foo })` -> `() => foo`
Expression::ArrowFunctionExpression(arrow_expr) => {
if !arrow_expr.expression
&& arrow_expr.body.directives.is_empty()
&& arrow_expr.body.statements.len() == 1
{
if let Some(body) = arrow_expr.body.statements.first_mut() {
if let Statement::ReturnStatement(ret_stmt) = body {
let return_stmt_arg =
ret_stmt.argument.as_mut().map(|arg| ctx.ast.move_expression(arg));

if let Some(return_stmt_arg) = return_stmt_arg {
*body = ctx.ast.statement_expression(SPAN, return_stmt_arg);
arrow_expr.expression = true;
self.changed = true;
}
}
}
}
}
Expression::BinaryExpression(expr) => {
self.compress_typeof_undefined(expr, ctx);

// Fold
if let Some(folded_expr) = match expr {
Expression::Identifier(ident) => self.try_compress_undefined(ident, ctx),
Expression::BooleanLiteral(_) => self.try_compress_boolean(expr, ctx),
Expression::AssignmentExpression(e) => Self::try_compress_assignment_expression(e, ctx),
Expression::LogicalExpression(e) => Self::try_compress_is_null_or_undefined(e, ctx),
Expression::NewExpression(e) => Self::try_fold_new_expression(e, ctx),
Expression::CallExpression(e) => {
Self::try_fold_literal_constructor_call_expression(e, ctx)
.or_else(|| Self::try_fold_simple_function_call(e, ctx))
}
_ => {}
Expression::TemplateLiteral(t) => Self::try_fold_template_literal(t, ctx),
Expression::BinaryExpression(e) => Self::try_compress_typeof_undefined(e, ctx),
_ => None,
} {
*expr = folded_expr;
self.changed = true;
}
}
}
Expand All @@ -161,14 +118,18 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
/* Utilities */

/// Transforms `undefined` => `void 0`
fn try_compress_undefined(&mut self, expr: &mut Expression<'a>, ctx: Ctx<'a, 'b>) {
fn try_compress_undefined(
&self,
ident: &IdentifierReference<'a>,
ctx: Ctx<'a, 'b>,
) -> Option<Expression<'a>> {
if self.in_fixed_loop {
return;
return None;
}
if ctx.is_expression_undefined(expr) {
*expr = ctx.ast.void_0(expr.span());
self.changed = true;
if !ctx.is_identifier_undefined(ident) {
return None;
}
Some(ctx.ast.void_0(ident.span))
}

/// Test `Object.defineProperty(exports, ...)`
Expand Down Expand Up @@ -207,48 +168,80 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {

/// Transforms boolean expression `true` => `!0` `false` => `!1`.
/// Do not compress `true` in `Object.defineProperty(exports, 'Foo', {enumerable: true, ...})`.
fn try_compress_boolean(&mut self, expr: &mut Expression<'a>, ctx: Ctx<'a, 'b>) {
fn try_compress_boolean(
&self,
expr: &mut Expression<'a>,
ctx: Ctx<'a, 'b>,
) -> Option<Expression<'a>> {
if self.in_fixed_loop {
return;
return None;
}
let Expression::BooleanLiteral(lit) = expr else { return };
if !self.in_define_export {
let parent = ctx.ancestry.parent();
let no_unary = {
if let Ancestor::BinaryExpressionRight(u) = parent {
!matches!(
u.operator(),
BinaryOperator::Addition // Other effect, like string concatenation.
let Expression::BooleanLiteral(lit) = expr else { return None };
if self.in_define_export {
return None;
}
let parent = ctx.ancestry.parent();
let no_unary = {
if let Ancestor::BinaryExpressionRight(u) = parent {
!matches!(
u.operator(),
BinaryOperator::Addition // Other effect, like string concatenation.
| BinaryOperator::Instanceof // Relational operator.
| BinaryOperator::In
| BinaryOperator::StrictEquality // It checks type, so we should not fold.
| BinaryOperator::StrictInequality
)
} else {
false
}
};
// XOR: We should use `!neg` when it is not in binary expression.
let num = ctx.ast.expression_numeric_literal(
SPAN,
if lit.value ^ no_unary { 0.0 } else { 1.0 },
None,
NumberBase::Decimal,
);
*expr = if no_unary {
num
)
} else {
ctx.ast.expression_unary(SPAN, UnaryOperator::LogicalNot, num)
};
self.changed = true;
false
}
};
// XOR: We should use `!neg` when it is not in binary expression.
let num = ctx.ast.expression_numeric_literal(
SPAN,
if lit.value ^ no_unary { 0.0 } else { 1.0 },
None,
NumberBase::Decimal,
);
Some(if no_unary {
num
} else {
ctx.ast.expression_unary(SPAN, UnaryOperator::LogicalNot, num)
})
}

/// `() => { return foo })` -> `() => foo`
fn try_compress_arrow_expression(
&mut self,
arrow_expr: &mut ArrowFunctionExpression<'a>,
ctx: Ctx<'a, 'b>,
) {
if !arrow_expr.expression
&& arrow_expr.body.directives.is_empty()
&& arrow_expr.body.statements.len() == 1
{
if let Some(body) = arrow_expr.body.statements.first_mut() {
if let Statement::ReturnStatement(ret_stmt) = body {
let return_stmt_arg =
ret_stmt.argument.as_mut().map(|arg| ctx.ast.move_expression(arg));

if let Some(return_stmt_arg) = return_stmt_arg {
*body = ctx.ast.statement_expression(SPAN, return_stmt_arg);
arrow_expr.expression = true;
self.changed = true;
}
}
}
}
}

/// Compress `typeof foo == "undefined"` into `typeof foo > "u"`
/// Enabled by `compress.typeofs`
fn compress_typeof_undefined(&mut self, expr: &mut BinaryExpression<'a>, ctx: Ctx<'a, 'b>) {
fn try_compress_typeof_undefined(
expr: &mut BinaryExpression<'a>,
ctx: Ctx<'a, 'b>,
) -> Option<Expression<'a>> {
if !matches!(expr.operator, BinaryOperator::Equality | BinaryOperator::StrictEquality) {
return;
return None;
}
let pair = Self::commutative_pair(
(&expr.left, &expr.right),
Expand All @@ -264,16 +257,11 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
None
},
);
let Some((_void_exp, id_ref)) = pair else {
return;
};
let (_void_exp, id_ref) = pair?;
let argument = Expression::Identifier(ctx.alloc(id_ref));
let left = ctx.ast.expression_unary(SPAN, UnaryOperator::Typeof, argument);
let right = ctx.ast.expression_string_literal(SPAN, "u", None);
let binary_expr =
ctx.ast.binary_expression(expr.span, left, BinaryOperator::GreaterThan, right);
*expr = binary_expr;
self.changed = true;
Some(ctx.ast.expression_binary(expr.span, left, BinaryOperator::GreaterThan, right))
}

/// Compress `foo === null || foo === undefined` into `foo == null`.
Expand All @@ -290,16 +278,11 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
ctx: Ctx<'a, 'b>,
) -> Option<Expression<'a>> {
let op = expr.operator;
if !matches!(op, LogicalOperator::Or | LogicalOperator::And) {
return None;
}
#[allow(clippy::match_wildcard_for_single_variants)]
let target_ops = match op {
LogicalOperator::Or => (BinaryOperator::StrictEquality, BinaryOperator::Equality),
LogicalOperator::And => (BinaryOperator::StrictInequality, BinaryOperator::Inequality),
_ => unreachable!(),
LogicalOperator::Coalesce => return None,
};

if let Some(new_expr) = Self::try_compress_is_null_or_undefined_for_left_and_right(
&expr.left,
&expr.right,
Expand All @@ -309,14 +292,12 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
) {
return Some(new_expr);
}

let Expression::LogicalExpression(left) = &mut expr.left else {
return None;
};
if left.operator != op {
return None;
}

Self::try_compress_is_null_or_undefined_for_left_and_right(
&left.right,
&expr.right,
Expand Down Expand Up @@ -445,36 +426,32 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
ctx: Ctx<'a, 'b>,
) -> Option<Expression<'a>> {
let target = expr.left.as_simple_assignment_target_mut()?;
if matches!(expr.operator, AssignmentOperator::Subtraction) {
match &expr.right {
Expression::NumericLiteral(num) if num.value.to_int_32() == 1 => {
if !matches!(expr.operator, AssignmentOperator::Subtraction) {
return None;
}
match &expr.right {
Expression::NumericLiteral(num) if num.value.to_int_32() == 1 => {
// The `_` will not be placed to the target code.
let target = std::mem::replace(
target,
ctx.ast.simple_assignment_target_identifier_reference(SPAN, "_"),
);
Some(ctx.ast.expression_update(SPAN, UpdateOperator::Decrement, true, target))
}
Expression::UnaryExpression(un)
if matches!(un.operator, UnaryOperator::UnaryNegation) =>
{
let Expression::NumericLiteral(num) = &un.argument else { return None };
(num.value.to_int_32() == 1).then(|| {
// The `_` will not be placed to the target code.
let target = std::mem::replace(
target,
ctx.ast.simple_assignment_target_identifier_reference(SPAN, "_"),
);
Some(ctx.ast.expression_update(SPAN, UpdateOperator::Decrement, true, target))
}
Expression::UnaryExpression(un)
if matches!(un.operator, UnaryOperator::UnaryNegation) =>
{
if let Expression::NumericLiteral(num) = &un.argument {
(num.value.to_int_32() == 1).then(|| {
// The `_` will not be placed to the target code.
let target = std::mem::replace(
target,
ctx.ast.simple_assignment_target_identifier_reference(SPAN, "_"),
);
ctx.ast.expression_update(SPAN, UpdateOperator::Increment, true, target)
})
} else {
None
}
}
_ => None,
ctx.ast.expression_update(SPAN, UpdateOperator::Increment, true, target)
})
}
} else {
None
_ => None,
}
}

Expand Down Expand Up @@ -645,19 +622,25 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
}
}

fn try_fold_chain_call_expression(
fn try_compress_chain_call_expression(
&mut self,
call_expr: &mut CallExpression<'a>,
chain_expr: &mut ChainExpression<'a>,
ctx: Ctx<'a, 'b>,
) {
// `window.Object?.()` -> `Object?.()`
if call_expr.arguments.is_empty() && Self::is_window_object(&call_expr.callee) {
call_expr.callee =
ctx.ast.expression_identifier_reference(call_expr.callee.span(), "Object");
self.changed = true;
if let ChainElement::CallExpression(call_expr) = &mut chain_expr.expression {
// `window.Object?.()` -> `Object?.()`
if call_expr.arguments.is_empty() && Self::is_window_object(&call_expr.callee) {
call_expr.callee =
ctx.ast.expression_identifier_reference(call_expr.callee.span(), "Object");
self.changed = true;
}
}
}

fn try_fold_template_literal(t: &TemplateLiteral, ctx: Ctx<'a, 'b>) -> Option<Expression<'a>> {
t.to_js_string().map(|val| ctx.ast.expression_string_literal(t.span(), val, None))
}

/// returns an `Array()` constructor call with zero, one, or more arguments, copying from the input
fn array_constructor_call(
arguments: Vec<'a, Argument<'a>>,
Expand Down
2 changes: 2 additions & 0 deletions tasks/minsize/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ oxc_allocator = { workspace = true }
oxc_codegen = { workspace = true }
oxc_minifier = { workspace = true }
oxc_parser = { workspace = true }
oxc_semantic = { workspace = true }
oxc_span = { workspace = true }
oxc_transformer = { workspace = true }

flate2 = { workspace = true }
oxc_tasks_common = { workspace = true }
Expand Down
Loading
Loading