feat(minifier): inline constant spread elements into arrays (#21095)

implements flattening logic used to inline constant spread elements into
arrays

fixes #17358
This commit is contained in:
Armano authored and GitHub committed 2026-04-23 12:35:22 +09:00
1 parent 5df2eb4182
commit f091d779a5
4 files changed
+99 -2

No files matched your search

+4 -1
View File
@@ -365,7 +365,10 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
Self::substitute_object_or_array_constructor(expr, ctx);
}
Expression::BooleanLiteral(_) => Self::substitute_boolean(expr, ctx),
Expression::ArrayExpression(_) => Self::substitute_array_expression(expr, ctx),
Expression::ArrayExpression(_) => {
Self::try_flatten_array_expression_elements(expr, ctx);
Self::substitute_array_expression(expr, ctx);
}
Expression::Identifier(_) => Self::inline_identifier_reference(expr, ctx),
_ => {}
}
@@ -1505,6 +1505,75 @@ impl<'a> PeepholeOptimizations {
ctx.state.changed = true;
}
/// Flatten the spread of constant array literals inside array expressions:
/// `[a, ...[1, 2, 3]]` -> `[a, 1, 2, 3]`
///
/// Elisions inside the spread source are converted to `void 0` to match iterator
/// semantics (spreading an elision should result in `undefined`):
/// `[a, ...[1, , 3]]` -> `[a, 1, void 0, 3]`
/// If there are two or more elisions, we keep the spread as-is to avoid causing
/// the code to be longer.
pub fn try_flatten_array_expression_elements(
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let Expression::ArrayExpression(array) = expr else {
return;
};
let (new_size, should_fold) =
array.elements.iter().fold((0, false), |(mut new_size, mut should_fold), arg| {
new_size += if let ArrayExpressionElement::SpreadElement(spread_el) = arg {
if let Expression::ArrayExpression(array_expr) = &spread_el.argument
&& array_expr
.elements
.iter()
.filter(|inner_el| inner_el.is_elision())
.count()
< 2
{
should_fold = true;
array_expr.elements.len()
} else {
1
}
} else {
1
};
(new_size, should_fold)
});
if !should_fold {
return;
}
let old_elements =
std::mem::replace(&mut array.elements, ctx.ast.vec_with_capacity(new_size));
let new_elements = &mut array.elements;
for elem in old_elements {
if let ArrayExpressionElement::SpreadElement(mut spread_el) = elem {
if let Expression::ArrayExpression(array_expr) = &mut spread_el.argument
&& array_expr.elements.iter().filter(|inner_el| inner_el.is_elision()).count()
< 2
{
for inner_el in array_expr.elements.drain(..) {
if let ArrayExpressionElement::Elision(elision) = inner_el {
new_elements.push(ctx.ast.void_0(elision.span).into());
} else {
new_elements.push(inner_el);
}
}
} else {
new_elements.push(ArrayExpressionElement::SpreadElement(spread_el));
}
} else {
new_elements.push(elem);
}
}
ctx.state.changed = true;
}
/// Transforms long array expression with string literals to `"str1,str2".split(',')`
pub fn substitute_array_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
// this threshold is chosen by hand by checking the minsize output
@@ -1007,10 +1007,12 @@ fn test_fold_array_length() {
// Not handled yet
fold("x = [,,1].length", "x = 3");
// Foldable after constant spread elements are inlined
fold("[...[1, 2, 3]].length", "3");
// Cannot fold
fold("x = [foo(), 0].length", "x = [foo(),0].length");
fold_same("x = y.length");
fold_same("[...[1, 2, 3]].length");
}
#[test]
@@ -958,3 +958,26 @@ fn test_flatten_nested_chain_expression() {
test_same("a?.b?.c");
test_same("(a?.b).c");
}
#[test]
fn test_flatten_array_spread_elements() {
test("var y = [3, 4, ...[1, 2]]", "var y = [3, 4, 1, 2]");
test("var y = [...[1, 2], 3, 4]", "var y = [1, 2, 3, 4]");
test("var y = [...[1, 2], ...[3, 4]]", "var y = [1, 2, 3, 4]");
test("var y = [1, ...[], 2]", "var y = [1, 2]");
test("var y = [...[1, , 3]]", "var y = [1, void 0, 3]");
test("var y = [...[1, ...[2, 3]]]", "var y = [1, 2, 3]");
test("var y = [1, , ...[2, 3]]", "var y = [1, , 2, 3]");
test_same("var y = [...x]");
test_same("var y = [1, ...x, 2]");
test("var x = [1, 2]; var y = [3, 4, ...x]", "var y = [3, 4, 1, 2]");
test("var x = [1, 2]; var y = [0, ...x, ...x]", "var x = [1, 2], y = [0, ...x, ...x]");
test("var x = [1, ...[2, 3]]; var y = [4, ...x]", "var y = [4, 1, 2, 3]");
test("var x = [1, , 3]; var y = [0, ...x]", "var y = [0, 1, void 0, 3]");
test("var x = []; var y = [1, ...x, 2]", "var y = [1, 2]");
test("var x = [1, 2]; var y = [0, , ...x]", "var y = [0, , 1, 2]");
test("var x = [1, 2]; var y = [0, ...x, 3]", "var y = [0, 1, 2, 3]");
test("var x=[30,40];var y = [10,...[],20,...x,50];", "var y = [10,20,30,40,50]");
test_same("var y = [0, ...[1, , , 3]]");
test("var y = [...[1, , ,], ...[, 2], , 2];", "var y = [...[1, , , ], void 0, 2, , 2];");
}