mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
feat(minifier): negate binary comparison for typeof x < 'u' (#26367)
Allow inversion of binary operation when comparing `'u'` string with `typeof `!(typeof a < "u")` => `typeof a > "u"` `!(typeof a > "u")` => `typeof a < "u"` this is needed to support negation of short version of `typeof x === "undefined"` -> `typeof a > "u"` generated by `substitute_typeof_undefined` fixes #25294 related #26460
This commit is contained in:
@@ -46,6 +46,14 @@ impl<'a> PeepholeOptimizations {
|
||||
binary_expr.operator = binary_expr.operator.equality_inverse_operator().unwrap();
|
||||
true
|
||||
}
|
||||
// `!(typeof a < "u")` => `typeof a > "u"`
|
||||
// `!(typeof a > "u")` => `typeof a < "u"`
|
||||
Expression::BinaryExpression(binary_expr)
|
||||
if Self::is_typeof_undefined_comparison(binary_expr) =>
|
||||
{
|
||||
binary_expr.operator = binary_expr.operator.compare_inverse_operator().unwrap();
|
||||
true
|
||||
}
|
||||
// `!0` => `1`
|
||||
// `!1` => `0`
|
||||
Expression::NumericLiteral(num) if boolean_context => {
|
||||
@@ -115,7 +123,8 @@ impl<'a> PeepholeOptimizations {
|
||||
let mut delta = 0;
|
||||
for side in [&e.left, &e.right] {
|
||||
match side {
|
||||
Expression::BinaryExpression(b) if b.operator.is_equality() => {}
|
||||
Expression::BinaryExpression(b)
|
||||
if b.operator.is_equality() || Self::is_typeof_undefined_comparison(b) => {}
|
||||
Expression::UnaryExpression(u) if u.operator.is_not() => {
|
||||
delta += if boolean_context { -1 } else { 1 }
|
||||
}
|
||||
@@ -153,6 +162,19 @@ impl<'a> PeepholeOptimizations {
|
||||
Some(delta)
|
||||
}
|
||||
|
||||
fn is_typeof_undefined_comparison(e: &BinaryExpression<'_>) -> bool {
|
||||
if !matches!(e.operator, BinaryOperator::LessThan | BinaryOperator::GreaterThan) {
|
||||
return false;
|
||||
}
|
||||
match (&e.left, &e.right) {
|
||||
(Expression::UnaryExpression(unary), literal)
|
||||
| (literal, Expression::UnaryExpression(unary)) => {
|
||||
unary.operator.is_typeof() && literal.is_specific_string_literal("u")
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply De Morgan's law in place. Only called on chains approved by
|
||||
/// [`Self::de_morgan_paren_delta`].
|
||||
fn de_morgan_invert_logical(
|
||||
|
||||
@@ -29,6 +29,9 @@ fn minimize_nots_with_de_morgan_comparison_chains() {
|
||||
test("if (!(a === b || c === d)) throw x;", "if (a !== b && c !== d) throw x;");
|
||||
// `&&` dual.
|
||||
test("if (!(a == b && c == d)) throw x;", "if (a != b || c != d) throw x;");
|
||||
// typeof
|
||||
test("if (!(typeof a < 'u' && !b)) throw x;", "if (typeof a > 'u' || b) throw x;");
|
||||
test("if (!(typeof a != 'undefined' && !b)) throw x;", "if (typeof a > 'u' || b) throw x;");
|
||||
// The fold is involutive, so the `if (!x) return` collapse (which negates
|
||||
// the test again) still reaches its old output.
|
||||
test(
|
||||
@@ -55,6 +58,7 @@ fn minimize_nots_with_de_morgan_comparison_chains() {
|
||||
fn minimize_nots_with_de_morgan_negative_cases() {
|
||||
// Relational comparisons don't invert freely (NaN), so the chain must stay.
|
||||
test_same("if (!(a < b || c < d)) throw x;");
|
||||
test_same("if (!(typeof a < 'u' || c < d)) throw x;");
|
||||
// A mixed operand would need a bare `!`; that fold is not involutive and can
|
||||
// regress shapes whose test is negated again later (e.g. branch swaps), so
|
||||
// it's left alone.
|
||||
@@ -81,6 +85,9 @@ fn minimize_nots_with_de_morgan_negative_cases() {
|
||||
|
||||
#[test]
|
||||
fn minimize_nots_with_binary_expressions() {
|
||||
test_same("var v = !(x > 'u');");
|
||||
test_same("var v = !(x > 0);");
|
||||
test_same("var v = !(typeof x > typeof y);");
|
||||
test("!(x === undefined)", "x");
|
||||
test("!(typeof(x) === 'undefined')", "");
|
||||
test("!(typeof(x()) === 'undefined')", "x()");
|
||||
@@ -91,4 +98,10 @@ fn minimize_nots_with_binary_expressions() {
|
||||
test("var k = !!(foo instanceof bar)", "var k = foo instanceof bar");
|
||||
test("!(a === 1 ? void 0 : a.b)", "a !== 1 && a.b;");
|
||||
test("!(a, b)", "a, b");
|
||||
test("var v = !(typeof x < 'u')", "var v = typeof x > 'u';");
|
||||
test("var v = !('u' > typeof x)", "var v = 'u' < typeof x;");
|
||||
test_same("var v = !(typeof x <= 'u');");
|
||||
test_same("var v = !(typeof x >= 'u');");
|
||||
test_same("var v = !(typeof x < 'string');");
|
||||
test_same("var v = !(typeof x > 'string');");
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
|
||||
|
||||
3.20 MB | 958.89 kB | 1.01 MB | 317.07 kB | 331.56 kB | 2 | echarts.js
|
||||
|
||||
6.69 MB | 2.12 MB | 2.31 MB | 453.44 kB | 488.28 kB | 5 | antd.js
|
||||
6.69 MB | 2.12 MB | 2.31 MB | 453.42 kB | 488.28 kB | 5 | antd.js
|
||||
|
||||
10.95 MB | 3.31 MB | 3.49 MB | 849.97 kB | 915.50 kB | 4 | typescript.js
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ antd.js:
|
||||
sys deallocs: 1038
|
||||
sys alloc bytes: 29976375 # 29.98 MB
|
||||
sys peak growth: 19934749 # 19.93 MB
|
||||
arena allocs: 207576
|
||||
arena allocs: 207570
|
||||
arena reallocs: 75941
|
||||
arena size: 45456840 # 45.46 MB
|
||||
|
||||
|
||||
Reference in New Issue
Block a user