code: private Node tryFoldLeftChildOp(Node n, Node left, Node right) {
      Token opType = n.getToken();
      checkState((NodeUtil.isAssociative(opType) && NodeUtil.isCommutative(opType)) || n.isAdd());
  
      checkState(!n.isAdd() || !NodeUtil.mayBeString(n, shouldUseTypes));
  
      // Use getNumberValue to handle constants like "NaN" and "Infinity"
      // other values are converted to numbers elsewhere.
      Double rightValObj = NodeUtil.getNumberValue(right);
      if (rightValObj != null && left.getToken() == opType) {
        checkState(left.hasTwoChildren());
  
        Node ll = left.getFirstChild();
        Node lr = ll.getNext();
  
        Node valueToCombine = ll;
        Node replacement = performArithmeticOp(opType, valueToCombine, right);
        if (replacement == null) {
          valueToCombine = lr;
          replacement = performArithmeticOp(opType, valueToCombine, right);
        }
        if (replacement != null) {
          // Remove the child that has been combined
          left.removeChild(valueToCombine);
          // Replace the left op with the remaining child.
          n.replaceChild(left, left.removeFirstChild());
          // New "-Infinity" node need location info explicitly
          // added.
          replacement.useSourceInfoIfMissingFromForTree(right);
          n.replaceChild(right, replacement);
          reportChangeToEnclosingScope(n);
        }
      }
  
      return n;
    }
docs: Expressions such as [foo() * 10 * 20] generate parse trees
  where no node has two const children ((foo() * 10) * 20), so
  performArithmeticOp() won't fold it -- tryFoldLeftChildOp() will.
  Specifically, it folds associative expressions where:
  - The left child is also an associative expression of the same time.
  - The right child is a constant NUMBER constant.
  - The left child's right child is a NUMBER constant.