fix(minifier): preserve delete semantic when folding non-finite numbers (#27466)

after folding constants to numbers we have to preserve behavior of
`delete`.
`delete (0/0)`, `delete (1/0)`, `delete +"a"` should evaluate to `true,
but if we convert them to `NaN` or `Infinity` this expression was
returning `false`

with this change we are going to convert them to `delete 0` to preserve
result

ref
https://github.com/oxc-project/oxc/issues/24331#delete-of-a-constant-folded-nan-changes-true-to-false
This commit is contained in:
Armano authored and GitHub committed 2026-10-09 10:09:46 +01:00
1 parent 989862fedf
commit 02577b142d
2 files changed
+30 -3

No files matched your search

@@ -9,7 +9,7 @@ use oxc_ecmascript::{
use oxc_span::{GetSpan, SPAN};
use oxc_syntax::operator::{AssignmentOperator, BinaryOperator, LogicalOperator};
use crate::TraverseCtx;
use crate::{TraverseCtx, generated::ancestor::Ancestor};
use super::PeepholeOptimizations;
@@ -30,7 +30,10 @@ impl<'a> PeepholeOptimizations {
UnaryOperator::UnaryNegation if e.argument.is_big_int_literal() => {}
_ if e.may_have_side_effects(ctx) => {}
_ => {
if let Some(changed) = e.evaluate_value(ctx).map(|v| ctx.value_to_expr(e.span, v)) {
if let Some(mut changed) =
e.evaluate_value(ctx).map(|v| ctx.value_to_expr(e.span, v))
{
Self::normalize_non_finite_number_for_delete(&mut changed, ctx);
ctx.replace_expression(expr, changed);
}
}
@@ -386,7 +389,8 @@ impl<'a> PeepholeOptimizations {
}
BinaryOperator::In => None,
};
if let Some(changed) = changed {
if let Some(mut changed) = changed {
Self::normalize_non_finite_number_for_delete(&mut changed, ctx);
ctx.replace_expression(expr, changed);
}
}
@@ -1019,6 +1023,15 @@ impl<'a> PeepholeOptimizations {
}
}
}
fn normalize_non_finite_number_for_delete(expr: &mut Expression<'a>, ctx: &TraverseCtx<'a>) {
if let Expression::NumericLiteral(num_expr) = expr
&& (num_expr.value.is_nan() || num_expr.value.is_infinite())
&& matches!(&ctx.parent(), Ancestor::UnaryExpressionArgument(unary) if unary.operator().is_delete())
{
num_expr.value = 0.0;
}
}
}
/// Outcome of folding an optional-chain at the deepest optional position.
@@ -1473,6 +1473,20 @@ fn test_issue_8782() {
fold("+(void unknown())", "+void unknown()");
}
#[test]
fn test_fold_delete_number() {
fold("delete (+'x')", "delete 0");
fold("delete (5 / 0)", "delete 0");
fold_same("delete (1 / 0)");
fold("delete (0 / -0)", "delete 0");
fold("delete (0 / 0)", "delete 0");
fold("delete (Infinity / Infinity)", "delete 0");
fold("delete (NaN / 0)", "delete 0");
fold("delete (1 / NaN)", "delete 0");
fold_same("delete (0, NaN)");
fold_same("delete (0, Infinity)");
}
#[test]
fn test_inline_values_in_template_literal() {
fold("`foo${1}`", "'foo1'");