Skip to content

Commit

Permalink
update implementation (incorrectly using yield*)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelficarra committed Dec 3, 2024
1 parent 26bd708 commit 2f213af
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 125 deletions.
79 changes: 20 additions & 59 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,31 @@ if (typeof Iterator === 'undefined' || Iterator == null) {
globalThis.Iterator = function() {};
}

function getIteratorFlattenable<A>(obj: IteratorOrIterable<A>, stringHandling: 'iterate-strings' | 'reject-strings'): Iterator<A>
function getIteratorFlattenable(obj: any, stringHandling: 'iterate-strings' | 'reject-strings'): Iterator<unknown>
function getIteratorFlattenable(obj: any, stringHandling: 'iterate-strings' | 'reject-strings'): Iterator<unknown> {
if (Object(obj) !== obj) {
if (stringHandling === 'reject-strings' || typeof obj != 'string') {
throw new TypeError;
}
obj = Object(obj);
}
let iter = Symbol.iterator in obj ? obj[Symbol.iterator]() : obj as Iterator<unknown>;
if (Object(iter) !== iter) {
throw new TypeError;
}
return iter;
}

type IteratorOrIterable<A> = Iterable<A> | Iterator<A>
interface Iterable<T> {
[Symbol.iterator](): Iterator<T, unknown, unknown>;
}

function liftIterator<A>(iter: Iterator<A>): Iterable<A> {
return { [Symbol.iterator]() { return iter; } };
}

function concatImpl<A>(...iterators: Array<IteratorOrIterable<A>>): Generator<A>
function concatImpl<A>(...iterables: Array<Iterable<A>>): Generator<A>
function concatImpl(): Generator<never>
function concatImpl<A>(iteratorA: IteratorOrIterable<A>): Generator<A>
function concatImpl<A, B>(iteratorA: IteratorOrIterable<A>, iteratorB: IteratorOrIterable<B>): Generator<A | B>
function concatImpl<A, B, C>(iteratorA: IteratorOrIterable<A>, iteratorB: IteratorOrIterable<B>, iteratorC: IteratorOrIterable<C>): Generator<A | B | C>
function concatImpl<A, B, C, D>(iteratorA: IteratorOrIterable<A>, iteratorB: IteratorOrIterable<B>, iteratorC: IteratorOrIterable<C>, iteratorD: IteratorOrIterable<D>): Generator<A | B | C | D>
function concatImpl<A, B, C, D, E>(iteratorA: IteratorOrIterable<A>, iteratorB: IteratorOrIterable<B>, iteratorC: IteratorOrIterable<C>, iteratorD: IteratorOrIterable<D>, iteratorE: IteratorOrIterable<E>): Generator<A | B | C | D | E>
function concatImpl(...iterators: Array<IteratorOrIterable<unknown>>): Generator<unknown>
function* concatImpl(...iterators: Array<unknown>): Generator<unknown> {
let i = 0;
try {
for (; i < iterators.length; ++i) {
let iter = iterators[i];
yield* liftIterator(getIteratorFlattenable(iter, 'reject-strings'));
}
} finally {
let err = null, hasErr = false;
for (++i; i < iterators.length; ++i) {
try {
let obj = iterators[i];
if (obj != null && (typeof obj === 'object' || typeof obj === 'function')) {
let iter = obj as Iterator<unknown>;
if (typeof iter.next === 'function') {
iter.return?.();
}
}
} catch (e) {
if (!hasErr) {
hasErr = true;
err = e;
}
}
}
if (hasErr) throw err;
function concatImpl<A>(iterableA: Iterable<A>): Generator<A>
function concatImpl<A, B>(iterableA: Iterable<A>, iterableB: Iterable<B>): Generator<A | B>
function concatImpl<A, B, C>(iterableA: Iterable<A>, iterableB: Iterable<B>, iterableC: Iterable<C>): Generator<A | B | C>
function concatImpl<A, B, C, D>(iterableA: Iterable<A>, iterableB: Iterable<B>, iterableC: Iterable<C>, iterableD: Iterable<D>): Generator<A | B | C | D>
function concatImpl<A, B, C, D, E>(iterableA: Iterable<A>, iterableB: Iterable<B>, iterableC: Iterable<C>, iterableD: Iterable<D>, iterableE: Iterable<E>): Generator<A | B | C | D | E>
function concatImpl(...iterables: Array<Iterable<unknown>>): Generator<unknown>
function concatImpl(...iterables: Array<unknown>): Generator<unknown> {
const openMethods = [];
for (let iterable of iterables) {
if (iterable == null || Object(iterable) !== iterable) throw new TypeError;
let openMethod = (iterable as any)[Symbol.iterator];
if (typeof openMethod !== 'function') throw new TypeError;
openMethods.push({openMethod, iterable});
}
return function*() {
for (let { openMethod, iterable } of openMethods) {
yield* { [Symbol.iterator]() { return openMethod.call(iterable); } }
}
}();
}

// NOTE: this line makes concat non-constructible, and gives it the appropriate name and length
const concat = (...iterators: Array<IteratorOrIterable<unknown>>) => concatImpl(...iterators);
const concat = (...iterators: Array<Iterable<unknown>>) => concatImpl(...iterators);
Object.defineProperty(Iterator, 'concat', {
configurable: true,
writable: true,
Expand Down
115 changes: 49 additions & 66 deletions test/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,64 +31,46 @@ test('concat', async t => {
});

test('concat does not iterate strings', async t => {
let it = Iterator.concat("ab", "cd");
assert.throws(
() => Array.from(it),
() => Iterator.concat("ab", "cd"),
TypeError,
);
});

test('concat bare iterators', async t => {
let count = 0;
const iter = {
next() {
if (count < 6) {
return { done: false, value: count++ };
} else {
return { done: true };
}
test('re-uses IteratorResult objects', async t => {
const iterResult = { done: false, value: 0 };
const iter = Iterator.concat({
[Symbol.iterator]() {
return {
next() {
return iterResult;
},
};
},
};
assert.deepEqual(
Array.from(Iterator.concat(
iter,
iter,
)),
[0, 1, 2, 3, 4, 5],
});
assert.equal(
iter.next(),
iterResult,
);
});

test('closes later iterators', async t => {
test('closes open iterators', async t => {
await t.test('stopped between iterators', () => {
let returned = [];
let concatted = Iterator.concat(
[0, 1],
[2, 3],
{
next() {
return { done: false, value: 'a' };
},
return() {
returned.push('a');
return { done: true, value: undefined };
},
},
{
next() {
return { done: false, value: 'b' };
},
return() {
returned.push('b');
return { done: true, value: undefined };
},
},
{
next() {
return { done: false, value: 'c' };
},
return() {
returned.push('c');
return { done: true, value: undefined };
[Symbol.iterator]() {
return {
next() {
return { done: false, value: 4 };
},
return() {
returned.push('a');
return { done: true, value: undefined };
},
};
},
},
);
Expand All @@ -98,7 +80,7 @@ test('closes later iterators', async t => {
assert.equal(concatted.next().value, 3);
assert.deepEqual(returned, []);
concatted.return();
assert.deepEqual(returned, ['a', 'b', 'c']);
assert.deepEqual(returned, []);
});

await t.test('stopped during iteration', () => {
Expand All @@ -107,30 +89,29 @@ test('closes later iterators', async t => {
[0, 1],
[2, 3],
{
next() {
return { done: false, value: 4 };
},
return() {
returned.push('a');
return { done: true, value: undefined };
[Symbol.iterator]() {
return {
next() {
return { done: false, value: 4 };
},
return() {
returned.push('a');
return { done: true, value: undefined };
},
};
},
},
{
next() {
return { done: false, value: 'b' };
},
return() {
returned.push('b');
return { done: true, value: undefined };
},
},
{
next() {
return { done: false, value: 'c' };
},
return() {
returned.push('c');
return { done: true, value: undefined };
[Symbol.iterator]() {
return {
next() {
return { done: false, value: 'b' };
},
return() {
returned.push('b');
return { done: true, value: undefined };
},
};
},
},
);
Expand All @@ -141,6 +122,8 @@ test('closes later iterators', async t => {
assert.equal(concatted.next().value, 4);
assert.deepEqual(returned, []);
concatted.return();
assert.deepEqual(returned, ['a', 'b', 'c']);
assert.deepEqual(returned, ['a']);
concatted.return();
assert.deepEqual(returned, ['a']);
});
});

0 comments on commit 2f213af

Please sign in to comment.