docs: public List<AbstractExpression> bindingToIndexedExpression(
              AbstractExpression expr) {
          // Defer the result construction for as long as possible on the
          // assumption that this function mostly gets applied to eliminate
          // negative cases.
          if (m_type != expr.m_type) {
              // The only allowed difference in expression types is between a
              // parameter and its original constant value.
              // That's handled in the independent override.
              return null;
          }
  
          // From here, this is much like the straight equality check,
          // except that this function and "equals" must each call themselves
          // in their recursions.
  
          // Delegating to this factored-out component of the "equals"
          // implementation eases simultaneous refinement of both methods.
          if ( ! hasEqualAttributes(expr)) {
              return null;
          }
          // The derived classes have verified that any added attributes
          // are identical.
  
          // Check that the presence, or lack, of children is the same
          if ((expr.m_left == null) != (m_left == null)) {
              return null;
          }
          if ((expr.m_right == null) != (m_right == null)) {
              return null;
          }
          if ((expr.m_args == null) != (m_args == null)) {
              return null;
          }
  
          // Check that the children identify themselves as matching
          List<AbstractExpression> leftBindings = null;
          if (m_left != null) {
              leftBindings = m_left.bindingToIndexedExpression(expr.m_left);
              if (leftBindings == null) {
                  return null;
              }
          }
          List<AbstractExpression> rightBindings = null;
          if (m_right != null) {
              rightBindings = m_right.bindingToIndexedExpression(expr.m_right);
              if (rightBindings == null) {
                  return null;
              }
          }
          List<AbstractExpression> argBindings = null;
          if (m_args != null) {
              if (m_args.size() != expr.m_args.size()) {
                  return null;
              }
              argBindings = new ArrayList<>();
              int ii = 0;
              // iterate the args lists in parallel, binding pairwise
              for (AbstractExpression rhs : expr.m_args) {
                  AbstractExpression lhs = m_args.get(ii++);
                  List<AbstractExpression> moreBindings =
                          lhs.bindingToIndexedExpression(rhs);
                  if (moreBindings == null) { // fail on any non-match
                      return null;
                  }
                  argBindings.addAll(moreBindings);
              }
          }
  
          // It's a match, so gather up the details.
          // It's rare (if even possible) for the same bound parameter to get
          // listed twice, so don't worry about duplicate entries, here.
          // That should not cause any issue for the caller.
          List<AbstractExpression> result = new ArrayList<>();
          if (leftBindings != null) { // null here can only mean no left child
              result.addAll(leftBindings);
          }
          if (rightBindings != null) { // null here can only mean no right child
              result.addAll(rightBindings);
          }
          if (argBindings != null) { // null here can only mean no args
              result.addAll(argBindings);
          }
          return result;
      }