setListIndex(path, index, value) {
    const objectId = path.length === 0 ? '_root' : path[path.length - 1].objectId
    const list = this.getObject(objectId)

    // Assignment past the end of the list => insert nulls followed by new value
    if (index >= list.length) {
      const insertions = createArrayOfNulls(index - list.length)
      insertions.push(value)
      return this.splice(path, list.length, 0, insertions)
    }
    if (list[index] instanceof Counter) {
      throw new RangeError('Cannot overwrite a Counter object; use .increment() or .decrement() to change its value.')
    }

    // If the assigned list element value is the same as the existing value, and
    // the assignment does not resolve a conflict, do nothing
    if (list[index] !== value || Object.keys(list[CONFLICTS][index] || {}).length > 1 || value === undefined) {
      this.applyAtPath(path, subpatch => {
        const pred = getPred(list, index)
        const opId = this.nextOpId()
        const valuePatch = this.setValue(objectId, index, value, false, pred, getElemId(list, index))
        subpatch.edits.push({action: 'update', index, opId, value: valuePatch})
      })
    }
  }
Updates the list object at path `path`, replacing the current value at
position `index` with the new value `value`.