Skip to content

Commit

Permalink
feat(minifier): fold .length
Browse files Browse the repository at this point in the history
  • Loading branch information
sapphi-red committed Dec 27, 2024
1 parent 75264ed commit 7a8cb60
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 4 deletions.
28 changes: 27 additions & 1 deletion crates/oxc_ecmascript/src/constant_evaluation/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{borrow::Cow, cmp::Ordering};

use num_bigint::BigInt;
use num_traits::Zero;
use num_traits::{ToPrimitive, Zero};

use oxc_ast::ast::*;

Expand Down Expand Up @@ -193,6 +193,7 @@ pub trait ConstantEvaluation<'a> {
Expression::StringLiteral(lit) => {
Some(ConstantValue::String(Cow::Borrowed(lit.value.as_str())))
}
Expression::StaticMemberExpression(e) => self.eval_static_member_expression(e),
_ => None,
}
}
Expand Down Expand Up @@ -437,6 +438,31 @@ pub trait ConstantEvaluation<'a> {
}
}

fn eval_static_member_expression(
&self,
expr: &StaticMemberExpression<'a>,
) -> Option<ConstantValue<'a>> {
match expr.property.name.as_str() {
"length" => {
if let Some(ConstantValue::String(s)) = self.eval_expression(&expr.object) {
// TODO(perf): no need to actually convert, only need the length
Some(ConstantValue::Number(s.encode_utf16().count().to_f64().unwrap()))
} else {
if expr.object.may_have_side_effects() {
return None;
}

if let Expression::ArrayExpression(arr) = &expr.object {
Some(ConstantValue::Number(arr.elements.len().to_f64().unwrap()))
} else {
None
}
}
}
_ => None,
}
}

/// <https://tc39.es/ecma262/#sec-abstract-relational-comparison>
fn is_less_than(
&self,
Expand Down
40 changes: 39 additions & 1 deletion crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ impl<'a> Traverse<'a> for PeepholeFoldConstants {
_ => ctx.eval_unary_expression(e).map(|v| ctx.value_to_expr(e.span, v)),
}
}
// TODO: return tryFoldGetProp(subtree);
Expression::StaticMemberExpression(e) => {
Self::try_fold_static_member_expression(e, ctx)
}
Expression::LogicalExpression(e) => Self::try_fold_logical_expression(e, ctx),
Expression::ChainExpression(e) => Self::try_fold_optional_chain(e, ctx),
// TODO: tryFoldGetElem
Expand Down Expand Up @@ -97,6 +99,16 @@ impl<'a, 'b> PeepholeFoldConstants {
None
}

fn try_fold_static_member_expression(
static_member_expr: &mut StaticMemberExpression<'a>,
ctx: Ctx<'a, 'b>,
) -> Option<Expression<'a>> {
// TODO: tryFoldObjectPropAccess(n, left, name)

ctx.eval_static_member_expression(static_member_expr)
.map(|value| ctx.value_to_expr(static_member_expr.span, value))
}

fn try_fold_logical_expression(
logical_expr: &mut LogicalExpression<'a>,
ctx: Ctx<'a, 'b>,
Expand Down Expand Up @@ -1503,6 +1515,31 @@ mod test {
test("(+x & 1) & 2", "+x & 0");
}

#[test]
fn test_fold_array_length() {
// Can fold
test("x = [].length", "x = 0");
test("x = [1,2,3].length", "x = 3");
// test("x = [a,b].length", "x = 2");

// Not handled yet
test("x = [,,1].length", "x = 3");

// Cannot fold
test("x = [foo(), 0].length", "x = [foo(),0].length");
test_same("x = y.length");
}

#[test]
fn test_fold_string_length() {
// Can fold basic strings.
test("x = ''.length", "x = 0");
test("x = '123'.length", "x = 3");

// Test Unicode escapes are accounted for.
test("x = '123\\u01dc'.length", "x = 4");
}

#[test]
fn test_fold_left_child_op() {
test_same("x & infinity & 2"); // FIXME: want x & 0
Expand Down Expand Up @@ -1548,6 +1585,7 @@ mod test {
test("x = +''", "x = 0");
test("x = +'+Infinity'", "x = Infinity");
test("x = +'-Infinity'", "x = -Infinity");
test("x = 'foo'.length", "x = 3");

for op in ["", "+", "-"] {
for s in ["inf", "infinity", "INFINITY", "InFiNiTy"] {
Expand Down
4 changes: 2 additions & 2 deletions tasks/minsize/minsize.snap
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Original | minified | minified | gzip | gzip | Fixture

555.77 kB | 274.26 kB | 270.13 kB | 91.26 kB | 90.80 kB | d3.js

1.01 MB | 461.13 kB | 458.89 kB | 126.91 kB | 126.71 kB | bundle.min.js
1.01 MB | 461.12 kB | 458.89 kB | 126.90 kB | 126.71 kB | bundle.min.js

1.25 MB | 657.23 kB | 646.76 kB | 164.23 kB | 163.73 kB | three.js

Expand All @@ -23,5 +23,5 @@ Original | minified | minified | gzip | gzip | Fixture

6.69 MB | 2.38 MB | 2.31 MB | 495.33 kB | 488.28 kB | antd.js

10.95 MB | 3.51 MB | 3.49 MB | 910.94 kB | 915.50 kB | typescript.js
10.95 MB | 3.51 MB | 3.49 MB | 910.88 kB | 915.50 kB | typescript.js

0 comments on commit 7a8cb60

Please sign in to comment.