11use oxc_allocator:: TakeIn ;
22use oxc_ast:: ast:: * ;
33use oxc_ecmascript:: {
4- ToJsString ,
4+ GlobalContext , ToJsString ,
55 constant_evaluation:: { ConstantEvaluation , ConstantValue , DetermineValueType , ValueType } ,
66 side_effects:: MayHaveSideEffects ,
77} ;
@@ -16,112 +16,107 @@ impl<'a> PeepholeOptimizations {
1616 /// Constant Folding
1717 ///
1818 /// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeFoldConstants.java>
19- pub fn fold_constants_exit_expression ( & self , expr : & mut Expression < ' a > , ctx : & mut Ctx < ' a , ' _ > ) {
20- match expr {
19+ pub fn fold_constants_exit_expression ( & self , e : & mut Expression < ' a > , ctx : & mut Ctx < ' a , ' _ > ) {
20+ match e {
2121 Expression :: TemplateLiteral ( t) => {
2222 self . try_inline_values_in_template_literal ( t, ctx) ;
2323 }
2424 Expression :: ObjectExpression ( e) => self . fold_object_spread ( e, ctx) ,
25+ Expression :: BinaryExpression ( _) => {
26+ Self :: try_fold_binary_expr ( e, ctx) ;
27+ Self :: try_fold_binary_typeof_comparison ( e, ctx) ;
28+ }
29+ Expression :: UnaryExpression ( _) => Self :: try_fold_unary_expr ( e, ctx) ,
30+ Expression :: StaticMemberExpression ( _) => Self :: try_fold_static_member_expr ( e, ctx) ,
31+ Expression :: ComputedMemberExpression ( _) => Self :: try_fold_computed_member_expr ( e, ctx) ,
32+ Expression :: LogicalExpression ( _) => Self :: try_fold_logical_expr ( e, ctx) ,
33+ Expression :: ChainExpression ( _) => Self :: try_fold_optional_chain ( e, ctx) ,
34+ Expression :: CallExpression ( _) => Self :: try_fold_number_constructor ( e, ctx) ,
2535 _ => { }
2636 }
27-
28- if let Some ( folded_expr) = match expr {
29- Expression :: BinaryExpression ( e) => Self :: try_fold_binary_expr ( e, ctx)
30- . or_else ( || Self :: try_fold_binary_typeof_comparison ( e, ctx) ) ,
31- Expression :: UnaryExpression ( e) => Self :: try_fold_unary_expr ( e, ctx) ,
32- Expression :: StaticMemberExpression ( e) => Self :: try_fold_static_member_expr ( e, ctx) ,
33- Expression :: ComputedMemberExpression ( e) => Self :: try_fold_computed_member_expr ( e, ctx) ,
34- Expression :: LogicalExpression ( e) => Self :: try_fold_logical_expr ( e, ctx) ,
35- Expression :: ChainExpression ( e) => Self :: try_fold_optional_chain ( e, ctx) ,
36- Expression :: CallExpression ( e) => Self :: try_fold_number_constructor ( e, ctx) ,
37- _ => None ,
38- } {
39- * expr = folded_expr;
40- ctx. state . changed = true ;
41- }
4237 }
4338
4439 #[ expect( clippy:: float_cmp) ]
45- fn try_fold_unary_expr (
46- e : & UnaryExpression < ' a > ,
47- ctx : & mut Ctx < ' a , ' _ > ,
48- ) -> Option < Expression < ' a > > {
40+ fn try_fold_unary_expr ( expr : & mut Expression < ' a > , ctx : & mut Ctx < ' a , ' _ > ) {
41+ let Expression :: UnaryExpression ( e) = expr else { return } ;
4942 match e. operator {
5043 // Do not fold `void 0` back to `undefined`.
51- UnaryOperator :: Void if e. argument . is_number_0 ( ) => None ,
44+ UnaryOperator :: Void if e. argument . is_number_0 ( ) => { }
5245 // Do not fold `true` and `false` back to `!0` and `!1`
53- UnaryOperator :: LogicalNot if matches ! ( & e. argument, Expression :: NumericLiteral ( lit) if lit. value == 0.0 || lit. value == 1.0 ) => {
54- None
55- }
46+ UnaryOperator :: LogicalNot if matches ! ( & e. argument, Expression :: NumericLiteral ( lit) if lit. value == 0.0 || lit. value == 1.0 ) =>
47+ { }
5648 // Do not fold big int.
57- UnaryOperator :: UnaryNegation if e. argument . is_big_int_literal ( ) => None ,
49+ UnaryOperator :: UnaryNegation if e. argument . is_big_int_literal ( ) => { }
50+ _ if e. may_have_side_effects ( ctx) => { }
5851 _ => {
59- if e. may_have_side_effects ( ctx) {
60- None
61- } else {
62- e. evaluate_value ( ctx) . map ( |v| ctx. value_to_expr ( e. span , v) )
52+ if let Some ( changed) = e. evaluate_value ( ctx) . map ( |v| ctx. value_to_expr ( e. span , v) ) {
53+ * expr = changed;
54+ ctx. state . changed = true ;
6355 }
6456 }
6557 }
6658 }
6759
68- fn try_fold_static_member_expr (
69- e : & StaticMemberExpression < ' a > ,
70- ctx : & mut Ctx < ' a , ' _ > ,
71- ) -> Option < Expression < ' a > > {
60+ fn try_fold_static_member_expr ( expr : & mut Expression < ' a > , ctx : & mut Ctx < ' a , ' _ > ) {
61+ let Expression :: StaticMemberExpression ( e) = expr else { return } ;
7262 // TODO: tryFoldObjectPropAccess(n, left, name)
7363 if e. object . may_have_side_effects ( ctx) {
74- None
75- } else {
76- e. evaluate_value ( ctx) . map ( |value| ctx. value_to_expr ( e. span , value) )
64+ return ;
65+ }
66+ if let Some ( changed) = e. evaluate_value ( ctx) . map ( |value| ctx. value_to_expr ( e. span , value) ) {
67+ * expr = changed;
68+ ctx. state . changed = true ;
7769 }
7870 }
7971
80- fn try_fold_computed_member_expr (
81- e : & ComputedMemberExpression < ' a > ,
82- ctx : & mut Ctx < ' a , ' _ > ,
83- ) -> Option < Expression < ' a > > {
72+ fn try_fold_computed_member_expr ( expr : & mut Expression < ' a > , ctx : & mut Ctx < ' a , ' _ > ) {
73+ let Expression :: ComputedMemberExpression ( e) = expr else { return } ;
8474 // TODO: tryFoldObjectPropAccess(n, left, name)
8575 if e. object . may_have_side_effects ( ctx) || e. expression . may_have_side_effects ( ctx) {
86- None
87- } else {
88- e. evaluate_value ( ctx) . map ( |value| ctx. value_to_expr ( e. span , value) )
76+ return ;
77+ }
78+ if let Some ( changed) = e. evaluate_value ( ctx) . map ( |value| ctx. value_to_expr ( e. span , value) ) {
79+ * expr = changed;
80+ ctx. state . changed = true ;
8981 }
9082 }
9183
92- fn try_fold_logical_expr (
93- logical_expr : & mut LogicalExpression < ' a > ,
94- ctx : & mut Ctx < ' a , ' _ > ,
95- ) -> Option < Expression < ' a > > {
96- match logical_expr. operator {
97- LogicalOperator :: And | LogicalOperator :: Or => Self :: try_fold_and_or ( logical_expr, ctx) ,
98- LogicalOperator :: Coalesce => Self :: try_fold_coalesce ( logical_expr, ctx) ,
84+ fn try_fold_logical_expr ( expr : & mut Expression < ' a > , ctx : & mut Ctx < ' a , ' _ > ) {
85+ let Expression :: LogicalExpression ( e) = expr else { return } ;
86+ if let Some ( changed) = match e. operator {
87+ LogicalOperator :: And | LogicalOperator :: Or => Self :: try_fold_and_or ( e, ctx) ,
88+ LogicalOperator :: Coalesce => Self :: try_fold_coalesce ( e, ctx) ,
89+ } {
90+ * expr = changed;
91+ ctx. state . changed = true ;
9992 }
10093 }
10194
102- fn try_fold_optional_chain (
103- chain_expr : & ChainExpression < ' a > ,
104- ctx : & mut Ctx < ' a , ' _ > ,
105- ) -> Option < Expression < ' a > > {
106- let left_expr = match & chain_expr. expression {
95+ fn try_fold_optional_chain ( expr : & mut Expression < ' a > , ctx : & mut Ctx < ' a , ' _ > ) {
96+ let Expression :: ChainExpression ( e) = expr else { return } ;
97+ let left_expr = match & e. expression {
10798 match_member_expression ! ( ChainElement ) => {
108- let member_expr = chain_expr . expression . to_member_expression ( ) ;
99+ let member_expr = e . expression . to_member_expression ( ) ;
109100 if !member_expr. optional ( ) {
110- return None ;
101+ return ;
111102 }
112103 member_expr. object ( )
113104 }
114105 ChainElement :: CallExpression ( call_expr) => {
115106 if !call_expr. optional {
116- return None ;
107+ return ;
117108 }
118109 & call_expr. callee
119110 }
120- ChainElement :: TSNonNullExpression ( _) => return None ,
111+ ChainElement :: TSNonNullExpression ( _) => return ,
121112 } ;
122113 let ty = left_expr. value_type ( ctx) ;
123- ( ty. is_null ( ) || ty. is_undefined ( ) )
124- . then ( || ctx. value_to_expr ( chain_expr. span , ConstantValue :: Undefined ) )
114+ if let Some ( changed) = ( ty. is_null ( ) || ty. is_undefined ( ) )
115+ . then ( || ctx. value_to_expr ( e. span , ConstantValue :: Undefined ) )
116+ {
117+ * expr = changed;
118+ ctx. state . changed = true ;
119+ }
125120 }
126121
127122 /// Try to fold a AND / OR node.
@@ -278,16 +273,14 @@ impl<'a> PeepholeOptimizations {
278273 }
279274
280275 #[ expect( clippy:: cast_possible_truncation, clippy:: cast_sign_loss) ]
281- fn try_fold_binary_expr (
282- e : & mut BinaryExpression < ' a > ,
283- ctx : & mut Ctx < ' a , ' _ > ,
284- ) -> Option < Expression < ' a > > {
276+ fn try_fold_binary_expr ( expr : & mut Expression < ' a > , ctx : & mut Ctx < ' a , ' _ > ) {
277+ let Expression :: BinaryExpression ( e) = expr else { return } ;
285278 // TODO: tryReduceOperandsForOp
286279
287280 // https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L1136
288281 // https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L1222
289282 let span = e. span ;
290- match e. operator {
283+ let changed = match e. operator {
291284 BinaryOperator :: Equality
292285 | BinaryOperator :: Inequality
293286 | BinaryOperator :: StrictEquality
@@ -339,30 +332,30 @@ impl<'a> PeepholeOptimizations {
339332 . filter ( |( _, right) | * right == 0.0 || right. is_nan ( ) || right. is_infinite ( ) )
340333 . and_then ( |_| ctx. eval_binary ( e) ) ,
341334 BinaryOperator :: ShiftLeft => {
342- if let Some ( ( left , right ) ) = Self :: extract_numeric_values ( e) {
335+ Self :: extract_numeric_values ( e) . and_then ( | ( left , right ) | {
343336 let result = e. evaluate_value ( ctx) ?. into_number ( ) ?;
344337 let left_len = Self :: approximate_printed_int_char_count ( left) ;
345338 let right_len = Self :: approximate_printed_int_char_count ( right) ;
346339 let result_len = Self :: approximate_printed_int_char_count ( result) ;
347- if result_len <= left_len + 2 + right_len {
348- return Some ( ctx. value_to_expr ( span, ConstantValue :: Number ( result) ) ) ;
349- }
350- }
351- None
340+ ( result_len <= left_len + 2 + right_len)
341+ . then ( || ctx. value_to_expr ( span, ConstantValue :: Number ( result) ) )
342+ } )
352343 }
353344 BinaryOperator :: ShiftRightZeroFill => {
354- if let Some ( ( left , right ) ) = Self :: extract_numeric_values ( e) {
345+ Self :: extract_numeric_values ( e) . and_then ( | ( left , right ) | {
355346 let result = e. evaluate_value ( ctx) ?. into_number ( ) ?;
356347 let left_len = Self :: approximate_printed_int_char_count ( left) ;
357348 let right_len = Self :: approximate_printed_int_char_count ( right) ;
358349 let result_len = Self :: approximate_printed_int_char_count ( result) ;
359- if result_len <= left_len + 3 + right_len {
360- return Some ( ctx. value_to_expr ( span, ConstantValue :: Number ( result) ) ) ;
361- }
362- }
363- None
350+ ( result_len <= left_len + 3 + right_len)
351+ . then ( || ctx. value_to_expr ( span, ConstantValue :: Number ( result) ) )
352+ } )
364353 }
365354 BinaryOperator :: In => None ,
355+ } ;
356+ if let Some ( changed) = changed {
357+ * expr = changed;
358+ ctx. state . changed = true ;
366359 }
367360 }
368361
@@ -544,21 +537,15 @@ impl<'a> PeepholeOptimizations {
544537 ) )
545538 }
546539
547- fn try_fold_number_constructor (
548- e : & CallExpression < ' a > ,
549- ctx : & mut Ctx < ' a , ' _ > ,
550- ) -> Option < Expression < ' a > > {
551- let Expression :: Identifier ( ident) = & e. callee else { return None } ;
552- if ident. name != "Number" {
553- return None ;
554- }
555- if !ctx. is_global_reference ( ident) {
556- return None ;
540+ fn try_fold_number_constructor ( expr : & mut Expression < ' a > , ctx : & mut Ctx < ' a , ' _ > ) {
541+ let Expression :: CallExpression ( e) = expr else { return } ;
542+ if !ctx. is_global_expr ( "Number" , & e. callee ) {
543+ return ;
557544 }
558545 if e. arguments . len ( ) != 1 {
559- return None ;
546+ return ;
560547 }
561- let arg = e. arguments [ 0 ] . as_expression ( ) ? ;
548+ let Some ( arg) = e. arguments [ 0 ] . as_expression ( ) else { return } ;
562549 let value = ConstantValue :: Number ( match arg {
563550 // `Number(undefined)` -> `NaN`
564551 Expression :: Identifier ( ident) if ctx. is_identifier_undefined ( ident) => f64:: NAN ,
@@ -574,27 +561,28 @@ impl<'a> PeepholeOptimizations {
574561 if let Some ( n) = arg. evaluate_value_to_number ( ctx) {
575562 n
576563 } else {
577- return Some ( ctx. ast . expression_unary (
564+ * expr = ctx. ast . expression_unary (
578565 e. span ,
579566 UnaryOperator :: UnaryPlus ,
580567 ctx. ast . expression_string_literal ( n. span , n. value , n. raw ) ,
581- ) ) ;
568+ ) ;
569+ ctx. state . changed = true ;
570+ return ;
582571 }
583572 }
584573 e if e. is_void_0 ( ) => f64:: NAN ,
585- _ => return None ,
574+ _ => return ,
586575 } ) ;
587- Some ( ctx. value_to_expr ( e. span , value) )
576+ * expr = ctx. value_to_expr ( e. span , value) ;
577+ ctx. state . changed = true ;
588578 }
589579
590- fn try_fold_binary_typeof_comparison (
591- bin_expr : & BinaryExpression < ' a > ,
592- ctx : & mut Ctx < ' a , ' _ > ,
593- ) -> Option < Expression < ' a > > {
580+ fn try_fold_binary_typeof_comparison ( expr : & mut Expression < ' a > , ctx : & mut Ctx < ' a , ' _ > ) {
581+ let Expression :: BinaryExpression ( e) = expr else { return } ;
594582 // `typeof a == typeof a` -> `true`, `typeof a != typeof a` -> `false`
595- if bin_expr . operator . is_equality ( ) {
583+ if e . operator . is_equality ( ) {
596584 if let ( Expression :: UnaryExpression ( left) , Expression :: UnaryExpression ( right) ) =
597- ( & bin_expr . left , & bin_expr . right )
585+ ( & e . left , & e . right )
598586 {
599587 if left. operator . is_typeof ( ) && right. operator . is_typeof ( ) {
600588 if let (
@@ -603,13 +591,13 @@ impl<'a> PeepholeOptimizations {
603591 ) = ( & left. argument , & right. argument )
604592 {
605593 if left_ident. name == right_ident. name {
606- return Some ( ctx . ast . expression_boolean_literal (
607- bin_expr . span ,
608- matches ! (
609- bin_expr . operator ,
610- BinaryOperator :: StrictEquality | BinaryOperator :: Equality
611- ) ,
612- ) ) ;
594+ let b = matches ! (
595+ e . operator ,
596+ BinaryOperator :: StrictEquality | BinaryOperator :: Equality
597+ ) ;
598+ * expr = ctx . ast . expression_boolean_literal ( e . span , b ) ;
599+ ctx . state . changed = true ;
600+ return ;
613601 }
614602 }
615603 }
@@ -618,18 +606,20 @@ impl<'a> PeepholeOptimizations {
618606
619607 // `typeof a === 'asd` -> `false``
620608 // `typeof a !== 'b'` -> `true``
621- if let Expression :: UnaryExpression ( left) = & bin_expr . left {
622- if left. operator . is_typeof ( ) && bin_expr . operator . is_equality ( ) {
623- let right_ty = bin_expr . right . value_type ( ctx) ;
609+ if let Expression :: UnaryExpression ( left) = & e . left {
610+ if left. operator . is_typeof ( ) && e . operator . is_equality ( ) {
611+ let right_ty = e . right . value_type ( ctx) ;
624612
625613 if !right_ty. is_undetermined ( ) && right_ty != ValueType :: String {
626- return Some ( ctx. ast . expression_boolean_literal (
627- bin_expr. span ,
628- bin_expr. operator == BinaryOperator :: Inequality
629- || bin_expr. operator == BinaryOperator :: StrictInequality ,
630- ) ) ;
614+ * expr = ctx. ast . expression_boolean_literal (
615+ e. span ,
616+ e. operator == BinaryOperator :: Inequality
617+ || e. operator == BinaryOperator :: StrictInequality ,
618+ ) ;
619+ ctx. state . changed = true ;
620+ return ;
631621 }
632- if let Expression :: StringLiteral ( string_lit) = & bin_expr . right {
622+ if let Expression :: StringLiteral ( string_lit) = & e . right {
633623 if !matches ! (
634624 string_lit. value. as_str( ) ,
635625 "string"
@@ -642,17 +632,16 @@ impl<'a> PeepholeOptimizations {
642632 | "function"
643633 | "unknown" // IE
644634 ) {
645- return Some ( ctx. ast . expression_boolean_literal (
646- bin_expr. span ,
647- bin_expr. operator == BinaryOperator :: Inequality
648- || bin_expr. operator == BinaryOperator :: StrictInequality ,
649- ) ) ;
635+ * expr = ctx. ast . expression_boolean_literal (
636+ e. span ,
637+ e. operator == BinaryOperator :: Inequality
638+ || e. operator == BinaryOperator :: StrictInequality ,
639+ ) ;
640+ ctx. state . changed = true ;
650641 }
651642 }
652643 }
653644 }
654-
655- None
656645 }
657646
658647 fn fold_object_spread ( & self , e : & mut ObjectExpression < ' a > , ctx : & mut Ctx < ' a , ' _ > ) {
0 commit comments