docs: function (doc, cb) {
      var self = this;
  
      if (self._sortSpecParts.length === 0)
        throw new Error("can't generate keys without a spec");
  
      // maps index -> ({'' -> value} or {path -> value})
      var valuesByIndexAndPath = [];
  
      var pathFromIndices = function (indices) {
        return indices.join(',') + ',';
      };
  
      var knownPaths = null;
  
      _.each(self._sortSpecParts, function (spec, whichField) {
        // Expand any leaf arrays that we find, and ignore those arrays
        // themselves.  (We never sort based on an array itself.)
        var branches = expandArraysInBranches(spec.lookup(doc), true);
  
        // If there are no values for a key (eg, key goes to an empty array),
        // pretend we found one null value.
        if (!branches.length)
          branches = [{value: null}];
  
        var usedPaths = false;
        valuesByIndexAndPath[whichField] = {};
        _.each(branches, function (branch) {
          if (!branch.arrayIndices) {
            // If there are no array indices for a branch, then it must be the
            // only branch, because the only thing that produces multiple branches
            // is the use of arrays.
            if (branches.length > 1)
              throw Error("multiple branches but no array used?");
            valuesByIndexAndPath[whichField][''] = branch.value;
            return;
          }
  
          usedPaths = true;
          var path = pathFromIndices(branch.arrayIndices);
          if (_.has(valuesByIndexAndPath[whichField], path))
            throw Error("duplicate path: " + path);
          valuesByIndexAndPath[whichField][path] = branch.value;
  
          // If two sort fields both go into arrays, they have to go into the
          // exact same arrays and we have to find the same paths.  This is
          // roughly the same condition that makes MongoDB throw this strange
          // error message.  eg, the main thing is that if sort spec is {a: 1,
          // b:1} then a and b cannot both be arrays.
          //
          // (In MongoDB it seems to be OK to have {a: 1, 'a.x.y': 1} where 'a'
          // and 'a.x.y' are both arrays, but we don't allow this for now.
          // #NestedArraySort
          // XXX achieve full compatibility here
          if (knownPaths && !_.has(knownPaths, path)) {
            throw Error("cannot index parallel arrays");
          }
        });
  
        if (knownPaths) {
          // Similarly to above, paths must match everywhere, unless this is a
          // non-array field.
          if (!_.has(valuesByIndexAndPath[whichField], '') &&
              _.size(knownPaths) !== _.size(valuesByIndexAndPath[whichField])) {
            throw Error("cannot index parallel arrays!");
          }
        } else if (usedPaths) {
          knownPaths = {};
          _.each(valuesByIndexAndPath[whichField], function (x, path) {
            knownPaths[path] = true;
          });
        }
      });
  
      if (!knownPaths) {
        // Easy case: no use of arrays.
        var soleKey = _.map(valuesByIndexAndPath, function (values) {
          if (!_.has(values, ''))
            throw Error("no value in sole key case?");
          return values[''];
        });
        cb(soleKey);
        return;
      }
  
      _.each(knownPaths, function (x, path) {
        var key = _.map(valuesByIndexAndPath, function (values) {
          if (_.has(values, ''))
            return values[''];
          if (!_.has(values, path))
            throw Error("missing path?");
          return values[path];
        });
        cb(key);
      });
    }