feat(minifier): fold BigInt(1n) -> 1n (#8270)

This commit is contained in:
Boshen 2025-01-06 07:40:25 +00:00
parent 676886fdab
commit b92b2abc2b

View file

@ -650,6 +650,7 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
} }
} }
/// Fold `Boolean`, `Number`, `String`, `BigInt` constructors.
fn try_fold_simple_function_call( fn try_fold_simple_function_call(
call_expr: &mut CallExpression<'a>, call_expr: &mut CallExpression<'a>,
ctx: Ctx<'a, 'b>, ctx: Ctx<'a, 'b>,
@ -657,62 +658,78 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
if call_expr.optional || call_expr.arguments.len() != 1 { if call_expr.optional || call_expr.arguments.len() != 1 {
return None; return None;
} }
if call_expr.callee.is_global_reference_name("Boolean", ctx.symbols()) { let Expression::Identifier(ident) = &call_expr.callee else { return None };
let name = ident.name.as_str();
if !matches!(name, "Boolean" | "Number" | "String" | "BigInt") {
return None;
}
let args = &mut call_expr.arguments;
if args.len() != 1 {
return None;
}
let arg = args[0].as_expression_mut()?;
if !ctx.is_global_reference(ident) {
return None;
}
match name {
// `Boolean(a)` -> `!!(a)` // `Boolean(a)` -> `!!(a)`
// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-boolean-constructor-boolean-value // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-boolean-constructor-boolean-value
// and // and
// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-logical-not-operator-runtime-semantics-evaluation // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-logical-not-operator-runtime-semantics-evaluation
"Boolean" => {
let arg = call_expr.arguments.get_mut(0).and_then(|arg| arg.as_expression_mut())?;
if let Expression::UnaryExpression(unary_expr) = arg { if let Expression::UnaryExpression(unary_expr) = arg {
if unary_expr.operator == UnaryOperator::LogicalNot { if unary_expr.operator == UnaryOperator::LogicalNot {
return Some(ctx.ast.move_expression(arg)); return Some(ctx.ast.move_expression(arg));
} }
} }
Some(
Some(ctx.ast.expression_unary( ctx.ast.expression_unary(
call_expr.span, call_expr.span,
UnaryOperator::LogicalNot, UnaryOperator::LogicalNot,
ctx.ast.expression_unary( ctx.ast.expression_unary(
call_expr.span, call_expr.span,
UnaryOperator::LogicalNot, UnaryOperator::LogicalNot,
ctx.ast.move_expression( ctx.ast.move_expression(
call_expr.arguments.get_mut(0).and_then(|arg| arg.as_expression_mut())?, call_expr
.arguments
.get_mut(0)
.and_then(|arg| arg.as_expression_mut())?,
), ),
), ),
)) ),
} else if call_expr.callee.is_global_reference_name("String", ctx.symbols()) { )
}
"String" => {
// `String(a)` -> `'' + (a)` // `String(a)` -> `'' + (a)`
let arg = call_expr.arguments.get_mut(0).and_then(|arg| arg.as_expression_mut())?;
if !matches!(arg, Expression::Identifier(_) | Expression::CallExpression(_)) if !matches!(arg, Expression::Identifier(_) | Expression::CallExpression(_))
&& !arg.is_literal() && !arg.is_literal()
{ {
return None; return None;
} }
Some(ctx.ast.expression_binary( Some(ctx.ast.expression_binary(
call_expr.span, call_expr.span,
ctx.ast.expression_string_literal(SPAN, "", None), ctx.ast.expression_string_literal(SPAN, "", None),
BinaryOperator::Addition, BinaryOperator::Addition,
ctx.ast.move_expression(arg), ctx.ast.move_expression(arg),
)) ))
} else if call_expr.callee.is_global_reference_name("Number", ctx.symbols()) { }
let number = call_expr "Number" => {
.arguments let number = arg.to_number()?;
.get_mut(0)
.and_then(|arg| arg.as_expression_mut())?
.to_number()?;
Some(ctx.ast.expression_numeric_literal( Some(ctx.ast.expression_numeric_literal(
call_expr.span, call_expr.span,
number, number,
None, None,
NumberBase::Decimal, NumberBase::Decimal,
)) ))
} else { }
None // `BigInt(1n)` -> `1n`
"BigInt" => {
if !matches!(arg, Expression::BigIntLiteral(_)) {
return None;
}
Some(ctx.ast.move_expression(arg))
}
_ => None,
} }
} }
@ -1218,20 +1235,6 @@ mod test {
test_same("goog.bind(f.m).call(g)"); test_same("goog.bind(f.m).call(g)");
} }
#[test]
fn test_simple_function_call1() {
test("var a = String(23)", "var a = '' + 23");
// Don't fold the existence check to preserve behavior
test_same("var a = String?.(23)");
test("var a = String('hello')", "var a = '' + 'hello'");
// Don't fold the existence check to preserve behavior
test_same("var a = String?.('hello')");
test_same("var a = String('hello', bar());");
test_same("var a = String({valueOf: function() { return 1; }});");
}
#[test] #[test]
fn test_simple_function_call2() { fn test_simple_function_call2() {
test("var a = Boolean(true)", "var a = !0"); test("var a = Boolean(true)", "var a = !0");
@ -1424,10 +1427,30 @@ mod test {
} }
#[test] #[test]
fn test_fold_number_call() { fn test_fold_string_constructor() {
test("var a = String(23)", "var a = '' + 23");
// Don't fold the existence check to preserve behavior
test_same("var a = String?.(23)");
test("var a = String('hello')", "var a = '' + 'hello'");
// Don't fold the existence check to preserve behavior
test_same("var a = String?.('hello')");
test_same("var a = String('hello', bar());");
test_same("var a = String({valueOf: function() { return 1; }});");
}
#[test]
fn test_fold_number_constructor() {
test("Number(0)", "0"); test("Number(0)", "0");
test("Number(true)", "1"); test("Number(true)", "1");
test("Number(false)", "0"); test("Number(false)", "0");
test("Number('foo')", "NaN"); test("Number('foo')", "NaN");
} }
#[test]
fn test_fold_big_int_constructor() {
test("BigInt(1n)", "1n");
test_same("BigInt(1)");
}
} }