From 01b7c28ed38296b89e99c92fc50e77988cf496c5 Mon Sep 17 00:00:00 2001 From: Conrad Buck Date: Sat, 8 Sep 2018 09:45:28 -0700 Subject: [PATCH] Remove IteratorSequence. Do not attempt to detect iterators. Fixes #1456 --- __tests__/IterableSeq.ts | 200 -------------------------------- __tests__/Seq.ts | 4 + src/Seq.js | 65 +---------- type-definitions/Immutable.d.ts | 15 ++- 4 files changed, 21 insertions(+), 263 deletions(-) delete mode 100644 __tests__/IterableSeq.ts diff --git a/__tests__/IterableSeq.ts b/__tests__/IterableSeq.ts deleted file mode 100644 index fe3b8ff146..0000000000 --- a/__tests__/IterableSeq.ts +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -/// - -declare var Symbol: any; -import { Seq } from '../'; - -describe('Sequence', () => { - it('creates a sequence from an iterable', () => { - const i = new SimpleIterable(); - const s = Seq(i); - expect(s.take(5).toArray()).toEqual([0, 1, 2, 3, 4]); - }); - - it('is stable', () => { - const i = new SimpleIterable(); - const s = Seq(i); - expect(s.take(5).toArray()).toEqual([0, 1, 2, 3, 4]); - expect(s.take(5).toArray()).toEqual([0, 1, 2, 3, 4]); - expect( - s - .take(5) - .take(Infinity) - .toArray() - ).toEqual([0, 1, 2, 3, 4]); - expect(s.take(5).toArray()).toEqual([0, 1, 2, 3, 4]); - }); - - it('counts iterations', () => { - const i = new SimpleIterable(10); - const s = Seq(i); - expect(s.forEach(x => x)).toEqual(10); - expect(s.take(5).forEach(x => x)).toEqual(5); - expect(s.forEach(x => x < 3)).toEqual(4); - }); - - it('creates a new iterator on every operations', () => { - const mockFn = jest.genMockFunction(); - const i = new SimpleIterable(3, mockFn); - const s = Seq(i); - expect(s.toArray()).toEqual([0, 1, 2]); - expect(mockFn.mock.calls).toEqual([[0], [1], [2]]); - // The iterator is recreated for the second time. - expect(s.toArray()).toEqual([0, 1, 2]); - expect(mockFn.mock.calls).toEqual([[0], [1], [2], [0], [1], [2]]); - }); - - it('can be iterated', () => { - const mockFn = jest.genMockFunction(); - const i = new SimpleIterable(3, mockFn); - const seq = Seq(i); - let entries = seq.entries(); - expect(entries.next()).toEqual({ value: [0, 0], done: false }); - // The iteration is lazy - expect(mockFn.mock.calls).toEqual([[0]]); - expect(entries.next()).toEqual({ value: [1, 1], done: false }); - expect(entries.next()).toEqual({ value: [2, 2], done: false }); - expect(entries.next()).toEqual({ value: undefined, done: true }); - expect(mockFn.mock.calls).toEqual([[0], [1], [2]]); - // The iterator is recreated for the second time. - entries = seq.entries(); - expect(entries.next()).toEqual({ value: [0, 0], done: false }); - expect(entries.next()).toEqual({ value: [1, 1], done: false }); - expect(entries.next()).toEqual({ value: [2, 2], done: false }); - expect(entries.next()).toEqual({ value: undefined, done: true }); - expect(mockFn.mock.calls).toEqual([[0], [1], [2], [0], [1], [2]]); - }); - - it('can be mapped and filtered', () => { - const mockFn = jest.genMockFunction(); - const i = new SimpleIterable(undefined, mockFn); // infinite - const seq = Seq(i) - .filter(x => x % 2 === 1) - .map(x => x * x); - const entries = seq.entries(); - expect(entries.next()).toEqual({ value: [0, 1], done: false }); - expect(entries.next()).toEqual({ value: [1, 9], done: false }); - expect(entries.next()).toEqual({ value: [2, 25], done: false }); - expect(mockFn.mock.calls).toEqual([[0], [1], [2], [3], [4], [5]]); - }); - - it('can be updated', () => { - function sum(seq) { - return seq.reduce((s, v) => s + v, 0); - } - const total = Seq([1, 2, 3]) - .filter(x => x % 2 === 1) - .map(x => x * x) - .update(sum); - expect(total).toBe(10); - }); - - describe('IteratorSequence', () => { - it('creates a sequence from a raw iterable', () => { - const i = new SimpleIterable(10); - const s = Seq(i[ITERATOR_SYMBOL]()); - expect(s.take(5).toArray()).toEqual([0, 1, 2, 3, 4]); - }); - - it('is stable', () => { - const i = new SimpleIterable(10); - const s = Seq(i[ITERATOR_SYMBOL]()); - expect(s.take(5).toArray()).toEqual([0, 1, 2, 3, 4]); - expect(s.take(5).toArray()).toEqual([0, 1, 2, 3, 4]); - expect(s.take(5).toArray()).toEqual([0, 1, 2, 3, 4]); - }); - - it('counts iterations', () => { - const i = new SimpleIterable(10); - const s = Seq(i[ITERATOR_SYMBOL]()); - expect(s.forEach(x => x)).toEqual(10); - expect(s.take(5).forEach(x => x)).toEqual(5); - expect(s.forEach(x => x < 3)).toEqual(4); - }); - - it('memoizes the iterator', () => { - const mockFn = jest.genMockFunction(); - const i = new SimpleIterable(10, mockFn); - const s = Seq(i[ITERATOR_SYMBOL]()); - expect(s.take(3).toArray()).toEqual([0, 1, 2]); - expect(mockFn.mock.calls).toEqual([[0], [1], [2]]); - - // Second call uses memoized values - expect(s.take(3).toArray()).toEqual([0, 1, 2]); - expect(mockFn.mock.calls).toEqual([[0], [1], [2]]); - - // Further ahead in the iterator yields more results. - expect(s.take(5).toArray()).toEqual([0, 1, 2, 3, 4]); - expect(mockFn.mock.calls).toEqual([[0], [1], [2], [3], [4]]); - }); - - it('can be iterated', () => { - const mockFn = jest.genMockFunction(); - const i = new SimpleIterable(3, mockFn); - const seq = Seq(i[ITERATOR_SYMBOL]()); - let entries = seq.entries(); - expect(entries.next()).toEqual({ value: [0, 0], done: false }); - // The iteration is lazy - expect(mockFn.mock.calls).toEqual([[0]]); - expect(entries.next()).toEqual({ value: [1, 1], done: false }); - expect(entries.next()).toEqual({ value: [2, 2], done: false }); - expect(entries.next()).toEqual({ value: undefined, done: true }); - expect(mockFn.mock.calls).toEqual([[0], [1], [2]]); - // The iterator has been memoized for the second time. - entries = seq.entries(); - expect(entries.next()).toEqual({ value: [0, 0], done: false }); - expect(entries.next()).toEqual({ value: [1, 1], done: false }); - expect(entries.next()).toEqual({ value: [2, 2], done: false }); - expect(entries.next()).toEqual({ value: undefined, done: true }); - expect(mockFn.mock.calls).toEqual([[0], [1], [2]]); - }); - - it('can iterate an skipped seq based on an iterator', () => { - const i = new SimpleIterable(4); - const seq = Seq(i[ITERATOR_SYMBOL]()); - expect(seq.size).toBe(undefined); - const skipped = seq.skip(2); - expect(skipped.size).toBe(undefined); - const iter = skipped[ITERATOR_SYMBOL](); - // The first two were skipped - expect(iter.next()).toEqual({ value: 2, done: false }); - expect(iter.next()).toEqual({ value: 3, done: false }); - expect(iter.next()).toEqual({ value: undefined, done: true }); - }); - }); -}); - -// Helper for this test -const ITERATOR_SYMBOL = - (typeof Symbol === 'function' && Symbol.iterator) || '@@iterator'; - -function SimpleIterable(max?: number, watcher?: any) { - this.max = max; - this.watcher = watcher; -} -SimpleIterable.prototype[ITERATOR_SYMBOL] = function() { - return new SimpleIterator(this); -}; - -function SimpleIterator(iterable) { - this.iterable = iterable; - this.value = 0; -} -SimpleIterator.prototype.next = function() { - if (this.value >= this.iterable.max) { - return { value: undefined, done: true }; - } - if (this.iterable.watcher) { - this.iterable.watcher(this.value); - } - return { value: this.value++, done: false }; -}; -SimpleIterator.prototype[ITERATOR_SYMBOL] = function() { - return this; -}; diff --git a/__tests__/Seq.ts b/__tests__/Seq.ts index 52ec342989..549c83b622 100644 --- a/__tests__/Seq.ts +++ b/__tests__/Seq.ts @@ -22,6 +22,10 @@ describe('Seq', () => { expect(Seq({ a: 1, b: 2, c: 3 }).size).toBe(3); }); + it('accepts an object with a next property', () => { + expect(Seq({ a: 1, b: 2, next: _ => _ }).size).toBe(3); + }); + it('accepts a collection string', () => { expect(Seq('foo').size).toBe(3); }); diff --git a/src/Seq.js b/src/Seq.js index f70c19fc60..22ec1bd75a 100644 --- a/src/Seq.js +++ b/src/Seq.js @@ -288,55 +288,6 @@ class CollectionSeq extends IndexedSeq { } } -class IteratorSeq extends IndexedSeq { - constructor(iterator) { - this._iterator = iterator; - this._iteratorCache = []; - } - - __iterateUncached(fn, reverse) { - if (reverse) { - return this.cacheResult().__iterate(fn, reverse); - } - const iterator = this._iterator; - const cache = this._iteratorCache; - let iterations = 0; - while (iterations < cache.length) { - if (fn(cache[iterations], iterations++, this) === false) { - return iterations; - } - } - let step; - while (!(step = iterator.next()).done) { - const val = step.value; - cache[iterations] = val; - if (fn(val, iterations++, this) === false) { - break; - } - } - return iterations; - } - - __iteratorUncached(type, reverse) { - if (reverse) { - return this.cacheResult().__iterator(type, reverse); - } - const iterator = this._iterator; - const cache = this._iteratorCache; - let iterations = 0; - return new Iterator(() => { - if (iterations >= cache.length) { - const step = iterator.next(); - if (step.done) { - return step; - } - cache[iterations] = step.value; - } - return iteratorValue(type, iterations, cache[iterations++]); - }); - } -} - // # pragma Helper functions export function isSeq(maybeSeq) { @@ -352,11 +303,9 @@ function emptySequence() { export function keyedSeqFromValue(value) { const seq = Array.isArray(value) ? new ArraySeq(value) - : isIterator(value) - ? new IteratorSeq(value) - : hasIterator(value) - ? new CollectionSeq(value) - : undefined; + : hasIterator(value) + ? new CollectionSeq(value) + : undefined; if (seq) { return seq.fromEntrySeq(); } @@ -395,9 +344,7 @@ function seqFromValue(value) { function maybeIndexedSeqFromValue(value) { return isArrayLike(value) ? new ArraySeq(value) - : isIterator(value) - ? new IteratorSeq(value) - : hasIterator(value) - ? new CollectionSeq(value) - : undefined; + : hasIterator(value) + ? new CollectionSeq(value) + : undefined; } diff --git a/type-definitions/Immutable.d.ts b/type-definitions/Immutable.d.ts index e493a78ba1..47e00c7e7a 100644 --- a/type-definitions/Immutable.d.ts +++ b/type-definitions/Immutable.d.ts @@ -3008,10 +3008,13 @@ declare module Immutable { * * If a `Seq`, that same `Seq`. * * If an `Collection`, a `Seq` of the same kind (Keyed, Indexed, or Set). * * If an Array-like, an `Seq.Indexed`. - * * If an Object with an Iterator, an `Seq.Indexed`. - * * If an Iterator, an `Seq.Indexed`. + * * If an Iterable Object, an `Seq.Indexed`. * * If an Object, a `Seq.Keyed`. * + * Note: An Iterator itself will be treated as an object, becoming a `Seq.Keyed`, + * which is usually not what you want. You should turn your Iterator Object into + * an iterable object by defining a Symbol.iterator (or @@iterator) method which + * returns `this`. */ export function Seq>(seq: S): S; export function Seq(collection: Collection.Keyed): Seq.Keyed; @@ -3723,13 +3726,17 @@ declare module Immutable { * * * If an `Collection`, that same `Collection`. * * If an Array-like, an `Collection.Indexed`. - * * If an Object with an Iterator, an `Collection.Indexed`. - * * If an Iterator, an `Collection.Indexed`. + * * If an Object with an Iterator defined, an `Collection.Indexed`. * * If an Object, an `Collection.Keyed`. * * This methods forces the conversion of Objects and Strings to Collections. * If you want to ensure that a Collection of one item is returned, use * `Seq.of`. + * + * Note: An Iterator itself will be treated as an object, becoming a `Seq.Keyed`, + * which is usually not what you want. You should turn your Iterator Object into + * an iterable object by defining a Symbol.iterator (or @@iterator) method which + * returns `this`. */ export function Collection>(collection: I): I; export function Collection(collection: Iterable): Collection.Indexed;