Repository: swc-project/swc
File: crates/swc_ecma_minifier/src/compress/optimize/iife.rs (Rust)

Code before:
                }

                // Optimize
                let new = self.ignore_return_value(&mut arg.expr);

                if let Some(new) = new {
                    arg.expr = Box::new(new);
                } else {
                    // Use `0` if it's removed.
                    arg.expr = Number {
                        span: arg.expr.span(),
                        value: 0.0,
                        raw: None,
                    }
                    .into();
                }
            } else {
                break;
            }
        }

        if e.args.len() > params.len() {
            for i in (params.len()..e.args.len()).rev() {
                if let Some(arg) = e.args.get_mut(i) {
                    let new = self.ignore_return_value(&mut arg.expr);

                    if let Some(new) = new {
                        arg.expr = Box::new(new);
                    } else {
                        e.args.remove(i);
                    }
                }
            }
        }
    }

    #[cfg_attr(feature = "debug", tracing::instrument(level = "debug", skip_all))]
    pub(super) fn inline_vars_in_node<N>(&mut self, n: &mut N, mut vars: FxHashMap<Id, Box<Expr>>)
    where
        N: for<'aa> VisitMutWith<NormalMultiReplacer<'aa>>,
    {
        trace_op!("inline: inline_vars_in_node");

        let mut v = NormalMultiReplacer::new(&mut vars, false);
        n.visit_mut_with(&mut v);
        self.changed |= v.changed;
    }

    fn may_invoke_iife(&self, call: &mut CallExpr) -> bool {
        if self.options.inline == 0
            && !(self.options.reduce_vars && self.options.reduce_fns && self.options.evaluate)

Review comment (suggestion):
The new trailing-argument removal loop ignores `arg.spread`. For a call like `(function(){})(...xs)` or `(function(a){})(x, ...xs)`, removing a spread argument is not semantics-preserving because spreading triggers iterator evaluation (can throw / have side effects) even if `arg.expr` itself looks side-effect-free. Add a guard to bail out when any argument has `spread`, or at least skip/abort removal when `arg.spread.is_some()` inside this loop.

Code after:
                }

                // Optimize
                let new = self.ignore_return_value(&mut arg.expr);

                if let Some(new) = new {
                    arg.expr = Box::new(new);
                } else {
                    // Use `0` if it's removed.
                    arg.expr = Number {
                        span: arg.expr.span(),
                        value: 0.0,
                        raw: None,
                    }
                    .into();
                }
            } else {
                break;
            }
        }

        if e.args.len() > params.len() && !params.iter().any(|p| p.is_rest()) {
            for i in (params.len()..e.args.len()).rev() {
                if let Some(arg) = e.args.get_mut(i) {
                    let new = self.ignore_return_value(&mut arg.expr);

                    if let Some(new) = new {
                        arg.expr = Box::new(new);
                    } else {
                        e.args.remove(i);
                    }
                }
            }
        }
    }

    #[cfg_attr(feature = "debug", tracing::instrument(level = "debug", skip_all))]
    pub(super) fn inline_vars_in_node<N>(&mut self, n: &mut N, mut vars: FxHashMap<Id, Box<Expr>>)
    where
        N: for<'aa> VisitMutWith<NormalMultiReplacer<'aa>>,
    {
        trace_op!("inline: inline_vars_in_node");

        let mut v = NormalMultiReplacer::new(&mut vars, false);
        n.visit_mut_with(&mut v);
        self.changed |= v.changed;
    }

    fn may_invoke_iife(&self, call: &mut CallExpr) -> bool {
        if self.options.inline == 0
            && !(self.options.reduce_vars && self.options.reduce_fns && self.options.evaluate)