@@ -7,6 +7,7 @@ use oxc_ecmascript::{
77} ;
88use oxc_span:: cmp:: ContentEq ;
99use oxc_span:: GetSpan ;
10+ use oxc_span:: SPAN ;
1011use oxc_syntax:: {
1112 es_target:: ESTarget ,
1213 identifier:: is_identifier_name,
@@ -134,6 +135,7 @@ impl<'a, 'b> PeepholeOptimizations {
134135 Self :: try_compress_assignment_to_update_expression ( e, ctx)
135136 }
136137 Expression :: LogicalExpression ( e) => Self :: try_compress_is_null_or_undefined ( e, ctx)
138+ . or_else ( || Self :: try_compress_is_object_and_not_null ( e, ctx) )
137139 . or_else ( || self . try_compress_logical_expression_to_assignment_expression ( e, ctx) )
138140 . or_else ( || Self :: try_rotate_logical_expression ( e, ctx) ) ,
139141 Expression :: TemplateLiteral ( t) => Self :: try_fold_template_literal ( t, ctx) ,
@@ -576,6 +578,171 @@ impl<'a, 'b> PeepholeOptimizations {
576578 }
577579 }
578580
581+ /// Compress `typeof foo === 'object' && foo !== null` into `typeof foo == 'object' && !!foo`.
582+ ///
583+ /// - `typeof foo === 'object' && foo !== null` => `typeof foo == 'object' && !!foo`
584+ /// - `typeof foo == 'object' && foo != null` => `typeof foo == 'object' && !!foo`
585+ /// - `typeof foo !== 'object' || foo === null` => `typeof foo != 'object' || !foo`
586+ /// - `typeof foo != 'object' || foo == null` => `typeof foo != 'object' || !foo`
587+ ///
588+ /// If `typeof foo == 'object'`, then `foo` is guaranteed to be an object or null.
589+ /// - If `foo` is an object, then `foo !== null` is `true`. If `foo` is null, then `foo !== null` is `false`.
590+ /// - If `foo` is an object, then `foo != null` is `true`. If `foo` is null, then `foo != null` is `false`.
591+ /// - If `foo` is an object, then `!!foo` is `true`. If `foo` is null, then `!!foo` is `false`.
592+ ///
593+ /// This compression is safe for `document.all` because `typeof document.all` is not `'object'`.
594+ fn try_compress_is_object_and_not_null (
595+ expr : & mut LogicalExpression < ' a > ,
596+ ctx : Ctx < ' a , ' _ > ,
597+ ) -> Option < Expression < ' a > > {
598+ let inversed = match expr. operator {
599+ LogicalOperator :: And => false ,
600+ LogicalOperator :: Or => true ,
601+ LogicalOperator :: Coalesce => return None ,
602+ } ;
603+
604+ if let Some ( new_expr) = Self :: try_compress_is_object_and_not_null_for_left_and_right (
605+ & expr. left ,
606+ & expr. right ,
607+ expr. span ,
608+ ctx,
609+ inversed,
610+ ) {
611+ return Some ( new_expr) ;
612+ }
613+
614+ let Expression :: LogicalExpression ( left) = & mut expr. left else {
615+ return None ;
616+ } ;
617+ let inversed = match expr. operator {
618+ LogicalOperator :: And => false ,
619+ LogicalOperator :: Or => true ,
620+ LogicalOperator :: Coalesce => return None ,
621+ } ;
622+
623+ Self :: try_compress_is_object_and_not_null_for_left_and_right (
624+ & left. right ,
625+ & expr. right ,
626+ Span :: new ( left. right . span ( ) . start , expr. span . end ) ,
627+ ctx,
628+ inversed,
629+ )
630+ . map ( |new_expr| {
631+ ctx. ast . expression_logical (
632+ expr. span ,
633+ ctx. ast . move_expression ( & mut left. left ) ,
634+ expr. operator ,
635+ new_expr,
636+ )
637+ } )
638+ }
639+
640+ fn try_compress_is_object_and_not_null_for_left_and_right (
641+ left : & Expression < ' a > ,
642+ right : & Expression < ' a > ,
643+ span : Span ,
644+ ctx : Ctx < ' a , ' b > ,
645+ inversed : bool ,
646+ ) -> Option < Expression < ' a > > {
647+ let pair = Self :: commutative_pair (
648+ ( & left, & right) ,
649+ |a_expr| {
650+ let Expression :: BinaryExpression ( a) = a_expr else { return None } ;
651+ let is_target_ops = if inversed {
652+ matches ! (
653+ a. operator,
654+ BinaryOperator :: StrictInequality | BinaryOperator :: Inequality
655+ )
656+ } else {
657+ matches ! ( a. operator, BinaryOperator :: StrictEquality | BinaryOperator :: Equality )
658+ } ;
659+ if !is_target_ops {
660+ return None ;
661+ }
662+ let ( id, ( ) ) = Self :: commutative_pair (
663+ ( & a. left , & a. right ) ,
664+ |a_a| {
665+ let Expression :: UnaryExpression ( a_a) = a_a else { return None } ;
666+ if a_a. operator != UnaryOperator :: Typeof {
667+ return None ;
668+ }
669+ let Expression :: Identifier ( id) = & a_a. argument else { return None } ;
670+ Some ( id)
671+ } ,
672+ |b| b. is_specific_string_literal ( "object" ) . then_some ( ( ) ) ,
673+ ) ?;
674+ Some ( ( id, a_expr) )
675+ } ,
676+ |b| {
677+ let Expression :: BinaryExpression ( b) = b else {
678+ return None ;
679+ } ;
680+ let is_target_ops = if inversed {
681+ matches ! ( b. operator, BinaryOperator :: StrictEquality | BinaryOperator :: Equality )
682+ } else {
683+ matches ! (
684+ b. operator,
685+ BinaryOperator :: StrictInequality | BinaryOperator :: Inequality
686+ )
687+ } ;
688+ if !is_target_ops {
689+ return None ;
690+ }
691+ let ( id, ( ) ) = Self :: commutative_pair (
692+ ( & b. left , & b. right ) ,
693+ |a_a| {
694+ let Expression :: Identifier ( id) = a_a else { return None } ;
695+ Some ( id)
696+ } ,
697+ |b| b. is_null ( ) . then_some ( ( ) ) ,
698+ ) ?;
699+ Some ( id)
700+ } ,
701+ ) ;
702+ let ( ( typeof_id_ref, typeof_binary_expr) , is_null_id_ref) = pair?;
703+ if typeof_id_ref. name != is_null_id_ref. name {
704+ return None ;
705+ }
706+ // It should also return None when the reference might refer to a reference value created by a with statement
707+ // when the minifier supports with statements
708+ if ctx. is_global_reference ( typeof_id_ref) {
709+ return None ;
710+ }
711+
712+ let mut new_left_expr = typeof_binary_expr. clone_in ( ctx. ast . allocator ) ;
713+ if let Expression :: BinaryExpression ( new_left_expr_binary) = & mut new_left_expr {
714+ new_left_expr_binary. operator =
715+ if inversed { BinaryOperator :: Inequality } else { BinaryOperator :: Equality } ;
716+ } else {
717+ unreachable ! ( ) ;
718+ }
719+
720+ let new_right_expr = if inversed {
721+ ctx. ast . expression_unary (
722+ SPAN ,
723+ UnaryOperator :: LogicalNot ,
724+ ctx. ast . expression_identifier_reference ( is_null_id_ref. span , is_null_id_ref. name ) ,
725+ )
726+ } else {
727+ ctx. ast . expression_unary (
728+ SPAN ,
729+ UnaryOperator :: LogicalNot ,
730+ ctx. ast . expression_unary (
731+ SPAN ,
732+ UnaryOperator :: LogicalNot ,
733+ ctx. ast
734+ . expression_identifier_reference ( is_null_id_ref. span , is_null_id_ref. name ) ,
735+ ) ,
736+ )
737+ } ;
738+ Some ( ctx. ast . expression_logical (
739+ span,
740+ new_left_expr,
741+ if inversed { LogicalOperator :: Or } else { LogicalOperator :: And } ,
742+ new_right_expr,
743+ ) )
744+ }
745+
579746 fn commutative_pair < ' x , A , F , G , RetF : ' x , RetG : ' x > (
580747 pair : ( & ' x A , & ' x A ) ,
581748 check_a : F ,
@@ -1838,6 +2005,55 @@ mod test {
18382005 test_same ( "(_foo = foo) === void 0 || bar === null" ) ;
18392006 }
18402007
2008+ #[ test]
2009+ fn test_fold_is_object_and_not_null ( ) {
2010+ test (
2011+ "var foo; v = typeof foo === 'object' && foo !== null" ,
2012+ "var foo; v = typeof foo == 'object' && !!foo" ,
2013+ ) ;
2014+ test (
2015+ "var foo; v = typeof foo == 'object' && foo !== null" ,
2016+ "var foo; v = typeof foo == 'object' && !!foo" ,
2017+ ) ;
2018+ test (
2019+ "var foo; v = typeof foo === 'object' && foo != null" ,
2020+ "var foo; v = typeof foo == 'object' && !!foo" ,
2021+ ) ;
2022+ test (
2023+ "var foo; v = typeof foo == 'object' && foo != null" ,
2024+ "var foo; v = typeof foo == 'object' && !!foo" ,
2025+ ) ;
2026+ test (
2027+ "var foo; v = typeof foo !== 'object' || foo === null" ,
2028+ "var foo; v = typeof foo != 'object' || !foo" ,
2029+ ) ;
2030+ test (
2031+ "var foo; v = typeof foo != 'object' || foo === null" ,
2032+ "var foo; v = typeof foo != 'object' || !foo" ,
2033+ ) ;
2034+ test (
2035+ "var foo; v = typeof foo !== 'object' || foo == null" ,
2036+ "var foo; v = typeof foo != 'object' || !foo" ,
2037+ ) ;
2038+ test (
2039+ "var foo; v = typeof foo != 'object' || foo == null" ,
2040+ "var foo; v = typeof foo != 'object' || !foo" ,
2041+ ) ;
2042+ test (
2043+ "var foo, bar; v = typeof foo === 'object' && foo !== null && bar !== 1" ,
2044+ "var foo, bar; v = typeof foo == 'object' && !!foo && bar !== 1" ,
2045+ ) ;
2046+ test (
2047+ "var foo, bar; v = bar !== 1 && typeof foo === 'object' && foo !== null" ,
2048+ "var foo, bar; v = bar !== 1 && typeof foo == 'object' && !!foo" ,
2049+ ) ;
2050+ test_same ( "var foo; v = typeof foo.a == 'object' && foo.a !== null" ) ; // cannot be folded because accessing foo.a might have a side effect
2051+ test_same ( "v = foo !== null && typeof foo == 'object'" ) ; // cannot be folded because accessing foo might have a side effect
2052+ test_same ( "v = typeof foo == 'object' && foo !== null" ) ; // cannot be folded because accessing foo might have a side effect
2053+ test_same ( "var foo, bar; v = typeof foo == 'object' && bar !== null" ) ;
2054+ test_same ( "var foo; v = typeof foo == 'string' && foo !== null" ) ;
2055+ }
2056+
18412057 #[ test]
18422058 fn test_fold_logical_expression_to_assignment_expression ( ) {
18432059 test ( "x || (x = 3)" , "x ||= 3" ) ;
0 commit comments