mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
fix(minifier): correct indopedency when processing nested sequences (#27273)
sequences in: - conditional expression: `a ? R : (b, (c, R));` -> `a || (b, c), R;` - logical to assignment expression: `a || (foo, (bar, a = b));` -> `a ||= (foo, (bar, b));` ref https://github.com/oxc-project/oxc/pull/27083#issuecomment-5941289663, https://github.com/oxc-project/oxc/pull/26029#discussion_r3839555572 https://github.com/oxc-project/monitor-oxc/actions/runs/36871613336/job/110401346237#step:6:727
This commit is contained in:
1 parent
f9a056b8a4
commit
565d75dce1
7 files changed
+80
-27
No files matched your search
@@ -147,10 +147,10 @@ impl<'a> PeepholeOptimizations {
|
||||
// `a ? d : (b, c, d)` => `(a || (b, c)), d`
|
||||
if let Expression::SequenceExpression(alternate) = &mut expr.alternate
|
||||
&& alternate.expressions.len() > 1
|
||||
&& let Some(last) = alternate.expressions.last()
|
||||
&& let Some(last) = Self::last_expression_in_sequence(alternate)
|
||||
&& ctx.expr_eq(last, &expr.consequent)
|
||||
{
|
||||
let last_expr = alternate.expressions.pop().unwrap();
|
||||
let last_expr = Self::pop_expression_from_sequence(alternate).unwrap();
|
||||
let seq_prefix = if alternate.expressions.len() == 1 {
|
||||
alternate.expressions.pop().unwrap()
|
||||
} else {
|
||||
@@ -176,10 +176,10 @@ impl<'a> PeepholeOptimizations {
|
||||
// `a ? (b, c, d) : d` => `(a && (b, c)), d`
|
||||
if let Expression::SequenceExpression(consequent) = &mut expr.consequent
|
||||
&& consequent.expressions.len() > 1
|
||||
&& let Some(last) = consequent.expressions.last()
|
||||
&& let Some(last) = Self::last_expression_in_sequence(consequent)
|
||||
&& ctx.expr_eq(last, &expr.alternate)
|
||||
{
|
||||
let last_expr = consequent.expressions.pop().unwrap();
|
||||
let last_expr = Self::pop_expression_from_sequence(consequent).unwrap();
|
||||
let seq_prefix = if consequent.expressions.len() == 1 {
|
||||
consequent.expressions.pop().unwrap()
|
||||
} else {
|
||||
|
||||
@@ -236,7 +236,7 @@ impl<'a> PeepholeOptimizations {
|
||||
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()
|
||||
Self::last_expression_in_sequence(sequence_expr)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -249,7 +249,7 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
let Expression::SequenceExpression(sequence_expr) = &mut e.right else { return };
|
||||
let Some(Expression::AssignmentExpression(assignment_expr)) =
|
||||
sequence_expr.expressions.pop()
|
||||
Self::pop_expression_from_sequence(sequence_expr)
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
use oxc_allocator::ArenaVec;
|
||||
use oxc_ast::ast::{Expression, SequenceExpression};
|
||||
use oxc_span::GetSpan;
|
||||
|
||||
use crate::TraverseCtx;
|
||||
|
||||
use super::PeepholeOptimizations;
|
||||
|
||||
impl<'a> PeepholeOptimizations {
|
||||
/// Joins two expressions into a sequence, preserving evaluation order.
|
||||
pub fn join_sequence(
|
||||
a: Expression<'a>,
|
||||
b: Expression<'a>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
if let Expression::SequenceExpression(mut sequence_expr) = a {
|
||||
// `(a, b); c`
|
||||
sequence_expr.expressions.push(b);
|
||||
return Expression::SequenceExpression(sequence_expr);
|
||||
}
|
||||
let span = a.span();
|
||||
let exprs = if let Expression::SequenceExpression(sequence_expr) = b {
|
||||
// `a; (b, c)`
|
||||
ArenaVec::from_iter_in(std::iter::once(a).chain(sequence_expr.unbox().expressions), ctx)
|
||||
} else {
|
||||
// `a; b`
|
||||
ArenaVec::from_array_in([a, b], ctx)
|
||||
};
|
||||
Expression::new_sequence_expression(span, exprs, ctx)
|
||||
}
|
||||
|
||||
/// Returns the final expression, descending through non-empty trailing sequences.
|
||||
/// For `(a, (b, c))`, returns `c`. An empty outer sequence returns [`None`];
|
||||
/// an empty trailing sequence is returned as-is.
|
||||
pub fn last_expression_in_sequence<'s>(
|
||||
seq: &'s SequenceExpression<'a>,
|
||||
) -> Option<&'s Expression<'a>> {
|
||||
let mut last = seq.expressions.last()?;
|
||||
while let Expression::SequenceExpression(inner) = last {
|
||||
let Some(next) = inner.expressions.last() else { break };
|
||||
last = next;
|
||||
}
|
||||
Some(last)
|
||||
}
|
||||
|
||||
/// Removes and returns the final expression, descending through trailing sequences
|
||||
/// and discarding any that become empty.
|
||||
/// For `(a, (b, c))`, returns `c` and leaves `(a, (b))`.
|
||||
/// Returns [`None`] if the outer sequence or a trailing nested sequence is already empty.
|
||||
pub fn pop_expression_from_sequence(
|
||||
seq: &mut SequenceExpression<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
if let Some(Expression::SequenceExpression(inner)) = seq.expressions.last_mut() {
|
||||
let last = Self::pop_expression_from_sequence(inner)?;
|
||||
if inner.expressions.is_empty() {
|
||||
seq.expressions.pop();
|
||||
}
|
||||
return Some(last);
|
||||
}
|
||||
|
||||
seq.expressions.pop()
|
||||
}
|
||||
}
|
||||
@@ -578,27 +578,6 @@ impl<'a> PeepholeOptimizations {
|
||||
ctx.replace_expression_with(target, |b, ctx| Self::join_sequence(a, b, ctx));
|
||||
}
|
||||
|
||||
pub fn join_sequence(
|
||||
a: Expression<'a>,
|
||||
b: Expression<'a>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
if let Expression::SequenceExpression(mut sequence_expr) = a {
|
||||
// `(a, b); c`
|
||||
sequence_expr.expressions.push(b);
|
||||
return Expression::SequenceExpression(sequence_expr);
|
||||
}
|
||||
let span = a.span();
|
||||
let exprs = if let Expression::SequenceExpression(sequence_expr) = b {
|
||||
// `a; (b, c)`
|
||||
ArenaVec::from_iter_in(std::iter::once(a).chain(sequence_expr.unbox().expressions), ctx)
|
||||
} else {
|
||||
// `a; b`
|
||||
ArenaVec::from_array_in([a, b], ctx)
|
||||
};
|
||||
Expression::new_sequence_expression(span, exprs, ctx)
|
||||
}
|
||||
|
||||
/// For variable declarations:
|
||||
/// * merge with the previous variable declarator if their kinds are the same
|
||||
/// * remove the variable declarator if it is unused
|
||||
|
||||
@@ -9,6 +9,7 @@ mod minimize_for_statement;
|
||||
mod minimize_if_statement;
|
||||
mod minimize_logical_expression;
|
||||
mod minimize_not_expression;
|
||||
mod minimize_sequences;
|
||||
mod minimize_statements;
|
||||
mod minimize_switch_statements;
|
||||
mod normalize;
|
||||
|
||||
@@ -46,8 +46,12 @@ fn minimize_conditional_exprs() {
|
||||
test("a ? b : c ? b : d", "a || c ? b : d");
|
||||
test("a ? c : (b, c)", "(a || b), c");
|
||||
test("a ? d : (b, c, d)", "a || (b, c), d;");
|
||||
test("a ? d : (b, (c, d));", "a || (b, c), d;");
|
||||
test("a ? d : (b, c, (d));", "a || (b, c), d;");
|
||||
test("a ? (b, c) : c", "(a && b), c");
|
||||
test("a ? (b, c, d) : d", "a && (b, c), d;");
|
||||
test("a ? (b, (c, d)) : d", "a && (b, c), d;");
|
||||
test("a ? (b, c, (d)) : d", "a && (b, c), d;");
|
||||
test("a ? b || c : c", "(a && b) || c");
|
||||
test("a ? c : b && c", "(a || b) && c");
|
||||
test(
|
||||
|
||||
@@ -1372,6 +1372,12 @@ fn test_fold_logical_expression_to_assignment_expression() {
|
||||
test("x || (a, x = g())", "x ||= (a, g())");
|
||||
test("x && (a, x = g())", "x &&= (a, g())");
|
||||
test("x ?? (a, x = g())", "x ??= (a, g())");
|
||||
test("x || (a, (b, x = 3))", "x ||= (a, b, 3)");
|
||||
test("x && (a, (b, x = 3))", "x &&= (a, b, 3)");
|
||||
test("x ?? (a, (b, x = 3))", "x ??= (a, b, 3)");
|
||||
test("x || (a, b, (x = 3))", "x ||= (a, b, 3)");
|
||||
test("x && (a, b, (x = 3))", "x &&= (a, b, 3)");
|
||||
test("x ?? (a, b, (x = 3))", "x ??= (a, b, 3)");
|
||||
test("var x; x.y || (a, x.y = 3)", "var x; x.y ||= (a, 3)");
|
||||
test("var x; x.y && (a, x.y = 3)", "var x; x.y &&= (a, 3)");
|
||||
test("var x; x.y ?? (a, x.y = 3)", "var x; x.y ??= (a, 3)");
|
||||
|
||||
Reference in new issue
Block a user