fix(minifier): recognize object spread of object literals as side-effect-free (#20299)

## Summary

- Add `ObjectExpression` handling in `ObjectPropertyKind::SpreadProperty`'s `may_have_side_effects` — spreading an object literal is side-effect-free when all its properties are
- Fix `remove_unused_object_expr` to check side effects for all-spread objects instead of unconditionally bailing

Closes rolldown/rolldown#8582
This commit is contained in:
Boshen committed 2026-03-13 04:56:43 +00:00
1 parent 4ce33e8463
commit 5c97b14bbc
4 files changed
+23 -4

No files matched your search

@@ -650,6 +650,10 @@ impl<'a> MayHaveSideEffects<'a> for ObjectPropertyKind<'a> {
} else {
match &e.argument {
Expression::ArrayExpression(arr) => arr.may_have_side_effects(ctx),
Expression::ObjectExpression(obj) => obj
.properties
.iter()
.any(|property| property.may_have_side_effects(ctx)),
Expression::StringLiteral(_) => false,
Expression::TemplateLiteral(t) => t.may_have_side_effects(ctx),
_ => true,
@@ -336,7 +336,12 @@ impl<'a> PeepholeOptimizations {
return true;
}
if object_expr.properties.iter().all(ObjectPropertyKind::is_spread) {
return false;
// All-spread objects like `({...x})` can only be removed if
// the spread arguments themselves have no side effects.
return !object_expr
.properties
.iter()
.any(|property| property.may_have_side_effects(ctx));
}
let mut transformed_elements = ctx.ast.vec();
@@ -310,10 +310,11 @@ fn closure_compiler_tests() {
test("new Object(...true)", true);
// OBJECT_SPREAD
// These could all invoke getters.
// Spreading unknown identifiers could invoke getters.
test("({...x})", true);
test("({...{}})", true);
test("({...{a:1}})", true);
// Spreading object literals is side-effect-free if the properties are.
test("({...{}})", false);
test("({...{a:1}})", false);
test("({...{a:i++}})", true);
test("({...{a:f()}})", true);
test("({...f()})", true);
@@ -711,6 +712,11 @@ fn test_object_expression() {
test("({...`foo${foo}`})", true);
test("({...`foo${foo()}`})", true);
test("({...foo()})", true);
// Spreading object literals
test("({...{}})", false);
test("({...{a: 1}})", false);
test("({...{a: foo()}})", true);
test("({...{[foo()]: 1}})", true);
}
#[test]
@@ -176,6 +176,10 @@ fn test_object_literal() {
// Object-spread may trigger getters.
test_same("({...a})");
test_same("({...foo()})");
// Spreading object literals is safe if contents are safe.
test("({...{}})", "");
test("({...{a: 1}})", "");
test("({...{a: foo()}})", "foo()");
test("({ [{ foo: foo() }]: 0 })", "foo()");
test("({ foo: { foo: foo() } })", "foo()");