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:
Armano authored and GitHub committed 2026-10-02 14:06:04 +01:00
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
+1
View File
@@ -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)");