Skip to content

Commit 0e614e3

Browse files
committed
Prevent clone by using lifetime
1 parent 824befd commit 0e614e3

File tree

1 file changed

+47
-24
lines changed

1 file changed

+47
-24
lines changed

crates/swc_ecma_minifier/src/compress/pure/evaluate.rs

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ impl Pure<'_> {
523523
},
524524
_ => return,
525525
};
526+
526527
if args.len() > 1
527528
|| args.first().is_some_and(|arg| {
528529
if arg.spread.is_some() {
@@ -535,8 +536,7 @@ impl Pure<'_> {
535536
return;
536537
}
537538

538-
let args = args.iter().map(|i| Some(i.clone())).collect::<Vec<_>>();
539-
let first_arg = get_first_arg(&args, &self.expr_ctx);
539+
let first_arg = get_first_arg(args.iter().map(Some), &self.expr_ctx);
540540

541541
if &*method.sym == "toFixed" {
542542
// https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.prototype.tofixed
@@ -1175,28 +1175,51 @@ fn f64_to_exponential_with_precision(n: f64, prec: usize) -> String {
11751175
res
11761176
}
11771177

1178-
fn get_first_arg(args: &Vec<Option<ExprOrSpread>>, expr_ctx: &ExprCtx) -> Option<Option<f64>> {
1179-
args.iter()
1180-
.find_map(|arg| {
1181-
if let Some(arg) = arg {
1182-
if arg.spread.is_some() {
1183-
return match arg.expr.as_array() {
1184-
Some(args) => {
1185-
if args.elems.is_empty() {
1186-
// next argument is used if the first spread argument is empty
1187-
// so we can't evaluate the call.
1188-
None
1189-
} else {
1190-
Some(get_first_arg(&args.elems, expr_ctx))
1191-
}
1192-
}
1193-
None => Some(None),
1194-
};
1195-
} else if !arg.expr.is_undefined(*expr_ctx) {
1196-
return Some(Some(eval_as_number(*expr_ctx, &arg.expr)));
1178+
/// Helper function to get the first argument of a call expression,
1179+
/// taking into account spread elements.
1180+
///
1181+
/// `Some(None)` means that the first argument is a spread element, but we can't
1182+
/// evaluate it.
1183+
///
1184+
/// `None` means that there is no argument.
1185+
fn get_first_arg<'a>(
1186+
args: impl IntoIterator<Item = Option<&'a ExprOrSpread>>,
1187+
expr_ctx: &ExprCtx,
1188+
) -> Option<Option<f64>> {
1189+
// A stack of iterators to process
1190+
let mut stack: Vec<Box<dyn Iterator<Item = Option<&'a ExprOrSpread>>>> =
1191+
vec![Box::new(args.into_iter())];
1192+
1193+
while let Some(current_iter) = stack.last_mut() {
1194+
// Get the next item from the top-most iterator
1195+
let Some(arg_opt) = current_iter.next() else {
1196+
// This iterator is exhausted, pop it and continue with the previous one
1197+
stack.pop();
1198+
continue;
1199+
};
1200+
1201+
if let Some(arg) = arg_opt {
1202+
if arg.spread.is_some() {
1203+
let Some(args_array) = arg.expr.as_array() else {
1204+
// It's a spread we can't evaluate (like ...x)
1205+
return Some(None);
1206+
};
1207+
if args_array.elems.is_empty() {
1208+
// next argument is used if the first spread argument is empty
1209+
// so we can't evaluate the call.
1210+
continue;
1211+
} else {
1212+
// Instead of recursing, push a new iterator onto the stack
1213+
stack.push(Box::new(args_array.elems.iter().map(Option::as_ref)));
1214+
continue;
11971215
}
1216+
} else if !arg.expr.is_undefined(*expr_ctx) {
1217+
// We found our first concrete argument!
1218+
return Some(eval_as_number(*expr_ctx, &arg.expr));
11981219
}
1199-
Some(None)
1200-
})
1201-
.unwrap_or(None)
1220+
}
1221+
}
1222+
1223+
// If we exhausted all iterators and found nothing
1224+
None
12021225
}

0 commit comments

Comments
 (0)