@@ -8,6 +8,7 @@ use oxc_ecmascript::{ToJsString, ToNumber, side_effects::MayHaveSideEffects};
88use oxc_semantic:: ReferenceFlags ;
99use oxc_span:: GetSpan ;
1010use oxc_span:: SPAN ;
11+ use oxc_syntax:: precedence:: GetPrecedence ;
1112use oxc_syntax:: {
1213 number:: NumberBase ,
1314 operator:: { BinaryOperator , UnaryOperator } ,
@@ -310,6 +311,71 @@ impl<'a> PeepholeOptimizations {
310311 ctx. state . changed = true ;
311312 }
312313
314+ /// Rotate associative binary operators:
315+ /// - `a | (b | c)` -> `(a | b) | c`
316+ ///
317+ /// Rotate commutative operators to reduce parentheses:
318+ /// - `a * (b % c)` -> `b % c * a`
319+ /// - `a * (b / c)` -> `b / c * a`
320+ pub fn substitute_rotate_binary_expression ( expr : & mut Expression < ' a > , ctx : & mut Ctx < ' a , ' _ > ) {
321+ let Expression :: BinaryExpression ( e) = expr else { return } ;
322+
323+ // Handle associative rotation
324+ let is_associative = matches ! (
325+ e. operator,
326+ BinaryOperator :: BitwiseOR | BinaryOperator :: BitwiseAnd | BinaryOperator :: BitwiseXOR
327+ ) ;
328+ if is_associative
329+ && let Expression :: BinaryExpression ( right) = & e. right
330+ && right. operator == e. operator
331+ && !right. right . may_have_side_effects ( ctx)
332+ {
333+ let Expression :: BinaryExpression ( mut right) = e. right . take_in ( ctx. ast ) else {
334+ return ;
335+ } ;
336+ let mut new_left = ctx. ast . expression_binary (
337+ e. span ,
338+ e. left . take_in ( ctx. ast ) ,
339+ e. operator ,
340+ right. left . take_in ( ctx. ast ) ,
341+ ) ;
342+ Self :: substitute_rotate_binary_expression ( & mut new_left, ctx) ;
343+ * expr = ctx. ast . expression_binary (
344+ e. span ,
345+ new_left,
346+ e. operator ,
347+ right. right . take_in ( ctx. ast ) ,
348+ ) ;
349+ ctx. state . changed = true ;
350+ return ;
351+ }
352+
353+ // Handle commutative rotation
354+ if let Expression :: BinaryExpression ( right) = & e. right
355+ && matches ! ( e. operator, BinaryOperator :: Multiplication )
356+ && e. operator . precedence ( ) == right. operator . precedence ( )
357+ {
358+ // Don't swap if left does not need a parentheses
359+ if let Expression :: BinaryExpression ( left) = & e. left
360+ && e. operator . precedence ( ) <= left. operator . precedence ( )
361+ {
362+ return ;
363+ }
364+
365+ // Don't swap if any of the value may have side effects as they may update the other values
366+ if !e. left . may_have_side_effects ( ctx)
367+ && !right. left . may_have_side_effects ( ctx)
368+ && !right. right . may_have_side_effects ( ctx)
369+ {
370+ let left = e. left . take_in ( ctx. ast ) ;
371+ let right = e. right . take_in ( ctx. ast ) ;
372+ e. right = left;
373+ e. left = right;
374+ ctx. state . changed = true ;
375+ }
376+ }
377+ }
378+
313379 /// Compress `typeof foo === 'object' && foo !== null` into `typeof foo == 'object' && !!foo`.
314380 ///
315381 /// - `typeof foo === 'object' && foo !== null` => `typeof foo == 'object' && !!foo`
@@ -1910,25 +1976,73 @@ mod test {
19101976 test_same ( "(f.bind(a))(b)" ) ;
19111977 }
19121978
1913- // FIXME: the cases commented out can be implemented
19141979 #[ test]
19151980 fn test_rotate_associative_operators ( ) {
1916- test ( "a || (b || c)" , "(a || b) || c" ) ;
1917- // float multiplication is not always associative
1918- // <https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-multiply>
1919- test_same ( "a * (b * c)" ) ;
1920- // test("a | (b | c)", "(a | b) | c");
1921- test_same ( "a % (b % c)" ) ;
1922- test_same ( "a / (b / c)" ) ;
1923- test_same ( "a - (b - c);" ) ;
1924- // test("a * (b % c);", "b % c * a");
1925- // test("a * (b / c);", "b / c * a");
1926- // cannot transform to `c / d * a * b`
1927- test_same ( "a * b * (c / d)" ) ;
1928- // test("(a + b) * (c % d)", "c % d * (a + b)");
1929- test_same ( "(a / b) * (c % d)" ) ;
1930- test_same ( "(c = 5) * (c % d)" ) ;
1931- // test("!a * c * (d % e)", "d % e * c * !a");
1981+ test (
1982+ "function f(a, b, c) { return a || (b || c) }" ,
1983+ "function f(a, b, c) { return (a || b) || c }" ,
1984+ ) ;
1985+ test (
1986+ "function f(a, b, c) { return a && (b && c) }" ,
1987+ "function f(a, b, c) { return (a && b) && c }" ,
1988+ ) ;
1989+ test (
1990+ "function f(a, b, c) { return a ?? (b ?? c) }" ,
1991+ "function f(a, b, c) { return (a ?? b) ?? c }" ,
1992+ ) ;
1993+
1994+ test (
1995+ "function f(a, b, c) { return a | (b | c) }" ,
1996+ "function f(a, b, c) { return (a | b) | c }" ,
1997+ ) ;
1998+ test (
1999+ "function f(a, b, c) { return a() | (b | c) }" ,
2000+ "function f(a, b, c) { return (a() | b) | c }" ,
2001+ ) ;
2002+ test (
2003+ "function f(a, b, c) { return a | (b() | c) }" ,
2004+ "function f(a, b, c) { return (a | b()) | c }" ,
2005+ ) ;
2006+ // c() will not be executed when `a | b` throws an error
2007+ test_same ( "function f(a, b, c) { return a | (b | c()) }" ) ;
2008+ test (
2009+ "function f(a, b, c) { return a & (b & c) }" ,
2010+ "function f(a, b, c) { return (a & b) & c }" ,
2011+ ) ;
2012+ test (
2013+ "function f(a, b, c) { return a ^ (b ^ c) }" ,
2014+ "function f(a, b, c) { return (a ^ b) ^ c }" ,
2015+ ) ;
2016+
2017+ // avoid rotation to prevent precision loss
2018+ // also multiplication is not associative due to floating point precision
2019+ // https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-multiply
2020+ test_same ( "function f(a, b, c) { return a + (b + c) }" ) ;
2021+ test_same ( "function f(a, b, c) { return a - (b - c) }" ) ;
2022+ test_same ( "function f(a, b, c) { return a / (b / c) }" ) ;
2023+ test_same ( "function f(a, b, c) { return a % (b % c) }" ) ;
2024+ test_same ( "function f(a, b, c) { return a ** (b ** c) }" ) ;
2025+
2026+ test (
2027+ "function f(a, b, c) { return a * (b % c) }" ,
2028+ "function f(a, b, c) { return b % c * a }" ,
2029+ ) ;
2030+ test_same ( "function f(a, b, c) { return a() * (b % c) }" ) ; // a may update b / c
2031+ test_same ( "function f(a, b, c) { return a * (b() % c) }" ) ; // b may update b / c
2032+ test_same ( "function f(a, b, c) { return a * (b % c()) }" ) ; // c may update b / c
2033+ test (
2034+ "function f(a, b, c) { return a * (b / c) }" ,
2035+ "function f(a, b, c) { return b / c * a }" ,
2036+ ) ;
2037+ test (
2038+ "function f(a, b, c) { return a * (b * c) }" ,
2039+ "function f(a, b, c) { return b * c * a }" ,
2040+ ) ;
2041+
2042+ test_same ( "function f(a, b, c, d) { return a * b * (c / d) }" ) ;
2043+ test_same ( "function f(a, b, c, d) { return (a + b) * (c % d) }" ) ;
2044+ // Don't swap if left has division (already high precedence)
2045+ test_same ( "function f(a, b, c, d) { return a / b * (c % d) }" ) ;
19322046 }
19332047
19342048 #[ test]
0 commit comments