mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
feat(minifier): annotate more pure constructors (#11555)
closes https://github.com/rolldown/rolldown/issues/2603 I used the following script to capture what argument throws. ```js function x(F) { try { new F(null) } catch (e) { console.log(F.name) } } x(Set) x(Map) x(WeakSet) x(WeakMap) x(AggregateError) x(Boolean) x(DataView) x(Error) x(EvalError) x(RangeError) x(ReferenceError) x(RegExp) x(SyntaxError) x(TypeError) x(URIError) x(ArrayBuffer) x(Date) x(Number) x(Object) x(String) ```
This commit is contained in:
1 parent
e13ed51788
commit
40ac1863b5
5 files changed
+316
-63
No files matched your search
@@ -1,6 +1,6 @@
|
||||
use oxc_ast::ast::{
|
||||
AssignmentExpression, AssignmentOperator, BinaryExpression, ConditionalExpression, Expression,
|
||||
LogicalExpression, LogicalOperator, UnaryExpression,
|
||||
LogicalExpression, LogicalOperator, StaticMemberExpression, UnaryExpression,
|
||||
};
|
||||
use oxc_syntax::operator::{BinaryOperator, UnaryOperator};
|
||||
|
||||
@@ -110,6 +110,7 @@ impl DetermineValueType for Expression<'_> {
|
||||
Expression::ConditionalExpression(e) => e.value_type(is_global_reference),
|
||||
Expression::LogicalExpression(e) => e.value_type(is_global_reference),
|
||||
Expression::ParenthesizedExpression(e) => e.expression.value_type(is_global_reference),
|
||||
Expression::StaticMemberExpression(e) => e.value_type(is_global_reference),
|
||||
_ => ValueType::Undetermined,
|
||||
}
|
||||
}
|
||||
@@ -272,3 +273,18 @@ impl DetermineValueType for LogicalExpression<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DetermineValueType for StaticMemberExpression<'_> {
|
||||
fn value_type(&self, is_global_reference: &impl IsGlobalReference) -> ValueType {
|
||||
if matches!(self.property.name.as_str(), "POSITIVE_INFINITY" | "NEGATIVE_INFINITY") {
|
||||
if let Some(ident) = self.object.get_identifier_reference() {
|
||||
if ident.name.as_str() == "Number"
|
||||
&& is_global_reference.is_global_reference(ident) == Some(true)
|
||||
{
|
||||
return ValueType::Number;
|
||||
}
|
||||
}
|
||||
}
|
||||
ValueType::Undetermined
|
||||
}
|
||||
}
|
||||
@@ -1250,14 +1250,14 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_coercion_substitution_boxed_number_vs_zero() {
|
||||
test_same("var x = new Number(0);\nif (x != 0) throw 'a';\n");
|
||||
test_same("var x = /* @__PURE__ */ new Number(0);\nif (x != 0) throw 'a';\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_coercion_substitution_boxed_primitives() {
|
||||
test_same("var x = new Number(); if (x != null) throw 'a';");
|
||||
test_same("var x = new String(); if (x != null) throw 'a';");
|
||||
test_same("var x = new Boolean();\nif (x != null) throw 'a';");
|
||||
test_same("var x = /* @__PURE__ */ new Number(); if (x != null) throw 'a';");
|
||||
test_same("var x = /* @__PURE__ */ new String(); if (x != null) throw 'a';");
|
||||
test_same("var x = /* @__PURE__ */ new Boolean();\nif (x != null) throw 'a';");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use oxc_allocator::{TakeIn, Vec};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::{
|
||||
constant_evaluation::{DetermineValueType, ValueType},
|
||||
side_effects::MayHaveSideEffects,
|
||||
};
|
||||
use oxc_ecmascript::constant_evaluation::{DetermineValueType, ValueType};
|
||||
use oxc_semantic::IsGlobalReference;
|
||||
use oxc_span::GetSpan;
|
||||
use oxc_syntax::scope::ScopeFlags;
|
||||
@@ -276,6 +273,8 @@ impl<'a> Normalize {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set `pure` on side effect free `new Expr()`s.
|
||||
/// `PC` or `PC_WITH_ARRAY` in <https://github.com/rollup/rollup/blob/v4.42.0/src/ast/nodes/shared/knownGlobals.ts>
|
||||
fn set_pure_or_no_side_effects_to_new_expr(
|
||||
new_expr: &mut NewExpression<'a>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
@@ -296,40 +295,79 @@ impl<'a> Normalize {
|
||||
// callee is a global reference.
|
||||
let ctx = Ctx(ctx);
|
||||
let len = new_expr.arguments.len();
|
||||
if match ident.name.as_str() {
|
||||
"WeakSet" | "WeakMap" if ctx.is_global_reference(ident) => match len {
|
||||
0 => true,
|
||||
1 => match new_expr.arguments[0].as_expression() {
|
||||
Some(Expression::NullLiteral(_)) => true,
|
||||
Some(Expression::ArrayExpression(e)) => e.elements.is_empty(),
|
||||
Some(e) if ctx.is_expression_undefined(e) => true,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
},
|
||||
"Date" if ctx.is_global_reference(ident) => match len {
|
||||
0 => true,
|
||||
1 => {
|
||||
let Some(arg) = new_expr.arguments[0].as_expression() else { return };
|
||||
let ty = arg.value_type(&ctx);
|
||||
matches!(
|
||||
ty,
|
||||
ValueType::Null
|
||||
| ValueType::Undefined
|
||||
| ValueType::Boolean
|
||||
| ValueType::Number
|
||||
| ValueType::String
|
||||
) && !arg.may_have_side_effects(&ctx)
|
||||
|
||||
let (zero_arg_throws_error, one_arg_array_throws_error, one_arg_throws_error): (
|
||||
bool,
|
||||
bool,
|
||||
&'static [ValueType],
|
||||
) = match ident.name.as_str() {
|
||||
"AggregateError" => (
|
||||
true,
|
||||
false,
|
||||
&[
|
||||
ValueType::Undefined,
|
||||
ValueType::Null,
|
||||
ValueType::Number,
|
||||
ValueType::BigInt,
|
||||
ValueType::Boolean,
|
||||
ValueType::Object,
|
||||
],
|
||||
),
|
||||
"DataView" => (
|
||||
true,
|
||||
true,
|
||||
&[
|
||||
ValueType::Undefined,
|
||||
ValueType::Null,
|
||||
ValueType::Number,
|
||||
ValueType::Boolean,
|
||||
ValueType::BigInt,
|
||||
ValueType::String,
|
||||
ValueType::Boolean,
|
||||
ValueType::Object,
|
||||
],
|
||||
),
|
||||
"Set" | "Map" | "WeakSet" | "WeakMap" => (
|
||||
false,
|
||||
false,
|
||||
&[
|
||||
ValueType::Number,
|
||||
ValueType::Boolean,
|
||||
ValueType::BigInt,
|
||||
ValueType::Boolean,
|
||||
ValueType::Object,
|
||||
],
|
||||
),
|
||||
"ArrayBuffer" | "Date" => (false, false, &[ValueType::BigInt]),
|
||||
"Boolean" | "Error" | "EvalError" | "RangeError" | "ReferenceError" | "RegExp"
|
||||
| "SyntaxError" | "TypeError" | "URIError" | "Number" | "Object" | "String" => {
|
||||
(false, false, &[])
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if !Self::can_set_pure(ident, ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if match len {
|
||||
0 if !zero_arg_throws_error => true,
|
||||
1 => match new_expr.arguments[0].as_expression() {
|
||||
Some(Expression::ArrayExpression(array_expr)) => {
|
||||
array_expr.elements.is_empty() && !one_arg_array_throws_error
|
||||
}
|
||||
Some(e) => {
|
||||
if let Expression::NewExpression(new_expr) = e {
|
||||
new_expr.pure
|
||||
} else {
|
||||
let value_type = e.value_type(&ctx);
|
||||
if value_type.is_undetermined() {
|
||||
false
|
||||
} else {
|
||||
!one_arg_throws_error.contains(&value_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
"Set" | "Map" if ctx.is_global_reference(ident) => match len {
|
||||
0 => true,
|
||||
1 => match new_expr.arguments[0].as_expression() {
|
||||
Some(Expression::NullLiteral(_)) => true,
|
||||
Some(e) if ctx.is_expression_undefined(e) => true,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
@@ -337,6 +375,12 @@ impl<'a> Normalize {
|
||||
new_expr.pure = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn can_set_pure(ident: &IdentifierReference<'a>, ctx: Ctx<'a, '_>) -> bool {
|
||||
ctx.is_global_reference(ident)
|
||||
// Throw is never pure.
|
||||
&& !matches!(ctx.parent(), Ancestor::ThrowStatementArgument(_))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -398,4 +442,196 @@ mod test {
|
||||
test("foo(Number.NaN)", "foo(NaN)");
|
||||
test_same("let Number; foo(Number.NaN)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pure_constructors() {
|
||||
test("new AggregateError", "AggregateError()");
|
||||
test("new ArrayBuffer", "");
|
||||
test("new Boolean", "");
|
||||
test("new DataView", "new DataView()");
|
||||
test("new Date", "");
|
||||
test("new Error", "");
|
||||
test("new EvalError", "");
|
||||
test("new Map", "");
|
||||
test("new Number", "");
|
||||
test("new Object", "");
|
||||
test("new RangeError", "");
|
||||
test("new ReferenceError", "");
|
||||
test("new RegExp", "");
|
||||
test("new Set", "");
|
||||
test("new String", "");
|
||||
test("new SyntaxError", "");
|
||||
test("new TypeError", "");
|
||||
test("new URIError", "");
|
||||
test("new WeakMap", "");
|
||||
test("new WeakSet", "");
|
||||
|
||||
test("new AggregateError(null)", "AggregateError(null)");
|
||||
test("new ArrayBuffer(null)", "");
|
||||
test("new Boolean(null)", "");
|
||||
test_same("new DataView(null)");
|
||||
test("new Date(null)", "");
|
||||
test("new Error(null)", "");
|
||||
test("new EvalError(null)", "");
|
||||
test("new Map(null)", "");
|
||||
test("new Number(null)", "");
|
||||
test("new Object(null)", "");
|
||||
test("new RangeError(null)", "");
|
||||
test("new ReferenceError(null)", "");
|
||||
test("new RegExp(null)", "");
|
||||
test("new Set(null)", "");
|
||||
test("new String(null)", "");
|
||||
test("new SyntaxError(null)", "");
|
||||
test("new TypeError(null)", "");
|
||||
test("new URIError(null)", "");
|
||||
test("new WeakMap(null)", "");
|
||||
test("new WeakSet(null)", "");
|
||||
|
||||
test("new AggregateError(undefined)", "AggregateError(void 0)");
|
||||
test("new ArrayBuffer(undefined)", "");
|
||||
test("new Boolean(undefined)", "");
|
||||
test_same("new DataView(void 0)");
|
||||
test("new Date(undefined)", "");
|
||||
test("new Error(undefined)", "");
|
||||
test("new EvalError(undefined)", "");
|
||||
test("new Map(undefined)", "");
|
||||
test("new Number(undefined)", "");
|
||||
test("new Object(undefined)", "");
|
||||
test("new RangeError(undefined)", "");
|
||||
test("new ReferenceError(undefined)", "");
|
||||
test("new RegExp(undefined)", "");
|
||||
test("new Set(undefined)", "");
|
||||
test("new String(undefined)", "");
|
||||
test("new SyntaxError(undefined)", "");
|
||||
test("new TypeError(undefined)", "");
|
||||
test("new URIError(undefined)", "");
|
||||
test("new WeakMap(undefined)", "");
|
||||
test("new WeakSet(undefined)", "");
|
||||
|
||||
test("new AggregateError(0)", "AggregateError(0)");
|
||||
test("new ArrayBuffer(0)", "");
|
||||
test("new Boolean(0)", "");
|
||||
test_same("new DataView(0)");
|
||||
test("new Date(0)", "");
|
||||
test("new Error(0)", "");
|
||||
test("new EvalError(0)", "");
|
||||
test_same("new Map(0)");
|
||||
test("new Number(0)", "");
|
||||
test("new Object(0)", "");
|
||||
test("new RangeError(0)", "");
|
||||
test("new ReferenceError(0)", "");
|
||||
test("new RegExp(0)", "");
|
||||
test_same("new Set(0)");
|
||||
test("new String(0)", "");
|
||||
test("new SyntaxError(0)", "");
|
||||
test("new TypeError(0)", "");
|
||||
test("new URIError(0)", "");
|
||||
test_same("new WeakMap(0)");
|
||||
test_same("new WeakSet(0)");
|
||||
|
||||
test("new AggregateError(10n)", "AggregateError(10n)");
|
||||
test_same("new ArrayBuffer(10n)");
|
||||
test("new Boolean(10n)", "");
|
||||
test_same("new DataView(10n)");
|
||||
test_same("new Date(10n)");
|
||||
test("new Error(10n)", "");
|
||||
test("new EvalError(10n)", "");
|
||||
test_same("new Map(10n)");
|
||||
test("new Number(10n)", "");
|
||||
test("new Object(10n)", "");
|
||||
test("new RangeError(10n)", "");
|
||||
test("new ReferenceError(10n)", "");
|
||||
test("new RegExp(10n)", "");
|
||||
test_same("new Set(10n)");
|
||||
test("new String(10n)", "");
|
||||
test("new SyntaxError(10n)", "");
|
||||
test("new TypeError(10n)", "");
|
||||
test("new URIError(10n)", "");
|
||||
test_same("new WeakMap(10n)");
|
||||
test_same("new WeakSet(10n)");
|
||||
|
||||
test("new AggregateError('')", "");
|
||||
test("new ArrayBuffer('')", "");
|
||||
test("new Boolean('')", "");
|
||||
test_same("new DataView('')");
|
||||
test("new Date('')", "");
|
||||
test("new Error('')", "");
|
||||
test("new EvalError('')", "");
|
||||
test("new Map('')", "");
|
||||
test("new Number('')", "");
|
||||
test("new Object('')", "");
|
||||
test("new RangeError('')", "");
|
||||
test("new ReferenceError('')", "");
|
||||
test("new RegExp('')", "");
|
||||
test("new Set('')", "");
|
||||
test("new String('')", "");
|
||||
test("new SyntaxError('')", "");
|
||||
test("new TypeError('')", "");
|
||||
test("new URIError('')", "");
|
||||
test("new WeakMap('')", "");
|
||||
test("new WeakSet('')", "");
|
||||
|
||||
test("new AggregateError(!0)", "AggregateError(!0)");
|
||||
test("new ArrayBuffer(!0)", "");
|
||||
test("new Boolean(!0)", "");
|
||||
test_same("new DataView(!0)");
|
||||
test("new Date(!0)", "");
|
||||
test("new Error(!0)", "");
|
||||
test("new EvalError(!0)", "");
|
||||
test_same("new Map(!0)");
|
||||
test("new Number(!0)", "");
|
||||
test("new Object(!0)", "");
|
||||
test("new RangeError(!0)", "");
|
||||
test("new ReferenceError(!0)", "");
|
||||
test("new RegExp(!0)", "");
|
||||
test_same("new Set(!0)");
|
||||
test("new String(!0)", "");
|
||||
test("new SyntaxError(!0)", "");
|
||||
test("new TypeError(!0)", "");
|
||||
test("new URIError(!0)", "");
|
||||
test_same("new WeakMap(!0)");
|
||||
test_same("new WeakSet(!0)");
|
||||
|
||||
test("new AggregateError([])", "");
|
||||
test("new ArrayBuffer([])", "");
|
||||
test("new Boolean([])", "");
|
||||
test_same("new DataView([])");
|
||||
test("new Date([])", "");
|
||||
test("new Error([])", "");
|
||||
test("new EvalError([])", "");
|
||||
test("new Map([])", "");
|
||||
test("new Number([])", "");
|
||||
test("new Object([])", "");
|
||||
test("new RangeError([])", "");
|
||||
test("new ReferenceError([])", "");
|
||||
test("new RegExp([])", "");
|
||||
test("new Set([])", "");
|
||||
test("new String([])", "");
|
||||
test("new SyntaxError([])", "");
|
||||
test("new TypeError([])", "");
|
||||
test("new URIError([])", "");
|
||||
test("new WeakMap([])", "");
|
||||
test("new WeakSet([])", "");
|
||||
|
||||
test("new AggregateError(a)", "AggregateError(a)");
|
||||
test_same("new ArrayBuffer(a)");
|
||||
test_same("new Boolean(a)");
|
||||
test_same("new DataView(a)");
|
||||
test_same("new Date(a)");
|
||||
test("new Error(a)", "Error(a)");
|
||||
test("new EvalError(a)", "EvalError(a)");
|
||||
test_same("new Map(a)");
|
||||
test_same("new Number(a)");
|
||||
test_same("new Object(a)");
|
||||
test("new RangeError(a)", "RangeError(a)");
|
||||
test("new ReferenceError(a)", "ReferenceError(a)");
|
||||
test_same("new RegExp(a)");
|
||||
test_same("new Set(a)");
|
||||
test_same("new String(a)");
|
||||
test("new SyntaxError(a)", "SyntaxError(a)");
|
||||
test("new TypeError(a)", "TypeError(a)");
|
||||
test("new URIError(a)", "URIError(a)");
|
||||
test_same("new WeakMap(a)");
|
||||
test_same("new WeakSet(a)");
|
||||
}
|
||||
}
|
||||
@@ -689,6 +689,7 @@ mod test {
|
||||
test("new WeakSet([])", "");
|
||||
test_same("new WeakSet([x])");
|
||||
test_same("new WeakSet(x)");
|
||||
test_same("throw new WeakSet()");
|
||||
test("new WeakMap()", "");
|
||||
test("new WeakMap(null)", "");
|
||||
test("new WeakMap(void 0)", "");
|
||||
|
||||
@@ -838,8 +838,6 @@ impl<'a> PeepholeOptimizations {
|
||||
return None;
|
||||
}
|
||||
if match name {
|
||||
"Error" | "AggregateError" | "Function" => true,
|
||||
_ if Self::is_native_error_name(name) => true,
|
||||
"RegExp" => {
|
||||
let arguments_len = e.arguments.len();
|
||||
arguments_len == 0
|
||||
@@ -849,18 +847,20 @@ impl<'a> PeepholeOptimizations {
|
||||
!ty.is_undetermined() && !ty.is_object()
|
||||
}))
|
||||
}
|
||||
"Error" | "AggregateError" | "Function" => true,
|
||||
_ if Self::is_native_error_name(name) => true,
|
||||
_ => unreachable!(),
|
||||
} {
|
||||
Some(ctx.ast.expression_call(
|
||||
return Some(ctx.ast.expression_call_with_pure(
|
||||
e.span,
|
||||
e.callee.take_in(ctx.ast),
|
||||
NONE,
|
||||
e.arguments.take_in(ctx.ast),
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
e.pure,
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Whether the name matches any native error name.
|
||||
@@ -1477,17 +1477,17 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_fold_new_expressions() {
|
||||
test("new Error()", "Error()");
|
||||
test("new Error('a')", "Error('a')");
|
||||
test("new Error('a', { cause: b })", "Error('a', { cause: b })");
|
||||
test("let _ = new Error()", "let _ = /* @__PURE__ */ Error()");
|
||||
test("let _ = new Error('a')", "let _ = /* @__PURE__ */ Error('a')");
|
||||
test("let _ = new Error('a', { cause: b })", "let _ = Error('a', { cause: b })");
|
||||
test_same("var Error; new Error()");
|
||||
test("new EvalError()", "EvalError()");
|
||||
test("new RangeError()", "RangeError()");
|
||||
test("new ReferenceError()", "ReferenceError()");
|
||||
test("new SyntaxError()", "SyntaxError()");
|
||||
test("new TypeError()", "TypeError()");
|
||||
test("new URIError()", "URIError()");
|
||||
test("new AggregateError()", "AggregateError()");
|
||||
test("let _ = new EvalError()", "let _ = /* @__PURE__ */ EvalError()");
|
||||
test("let _ = new RangeError()", "let _ = /* @__PURE__ */ RangeError()");
|
||||
test("let _ = new ReferenceError()", "let _ = /* @__PURE__ */ ReferenceError()");
|
||||
test("let _ = new SyntaxError()", "let _ = /* @__PURE__ */ SyntaxError()");
|
||||
test("let _ = new TypeError()", "let _ = /* @__PURE__ */ TypeError()");
|
||||
test("let _ = new URIError()", "let _ = /* @__PURE__ */ URIError()");
|
||||
test("let _ = new AggregateError('a')", "let _ = /* @__PURE__ */ AggregateError('a')");
|
||||
|
||||
test("new Function()", "Function()");
|
||||
test(
|
||||
@@ -1496,13 +1496,13 @@ mod test {
|
||||
);
|
||||
test_same("var Function; new Function()");
|
||||
|
||||
test("new RegExp()", "RegExp()");
|
||||
test("new RegExp('a')", "RegExp('a')");
|
||||
test("new RegExp(0)", "RegExp(0)");
|
||||
test("new RegExp(null)", "RegExp(null)");
|
||||
test("new RegExp()", "");
|
||||
test("new RegExp('a')", "");
|
||||
test("new RegExp(0)", "");
|
||||
test("new RegExp(null)", "");
|
||||
test("new RegExp('a', 'g')", "RegExp('a', 'g')");
|
||||
test_same("new RegExp(foo)");
|
||||
test_same("new RegExp(/foo/)");
|
||||
test("new RegExp(/foo/)", "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user