question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Implicit Symbol.iterator call in for..of loops / spread destructuring doesn't infer `this` generic type parameter

See original GitHub issue

TypeScript Version: 4.0.0-dev.20200507

Search Terms: Symbol.iterator type parameter implicit calling for…of loops object spread destructuring this generic

Code

What follows is 3 different ways of iterating over an object using a custom-made iterator. However, using Symbol.iterator implicitly via the for…of loop fails to produce the same behavior:

const obj = {
	x: 1,
	y: 2,
	z: 3,
	*[Symbol.iterator]<T extends object>(
		this: T,
	): Generator<NonNullable<{ [K in keyof T]: [K, NonNullable<T[K]>] }[keyof T]>, void, unknown> {
		for (const entry of Object.entries(this)) yield entry as never;
	},
};

{
	const iter = obj[Symbol.iterator]();

	for (let result = iter.next(); !result.done; result = iter.next()) {
		const { value } = result;
		value; // ["x", number] | ["y", number] | ["z", number]
	}
}

for (const value of obj[Symbol.iterator]()) {
	value; // ["x", number] | ["y", number] | ["z", number]
}

for (const value of obj) {
	value; // NonNullable<{ [K in keyof T]: [K, NonNullable<T[K]>]; }[keyof T]>
	// bad! This should be ["x", number] | ["y", number] | ["z", number]
}

// also, spread destructuring suffers from the same problem!
const values = [...obj]; // Array<NonNullable<{ [K in keyof T]: [K, NonNullable<T[K]>]; }[keyof T]>>
// bad! This should be Array<["x", number] | ["y", number] | ["z", number]>

Expected behavior: for (const value of obj) should implicitly do the type equivalent of calling our iterator symbol, i.e. for (const value of obj[Symbol.iterator]()). That means our type parameter should be correctly inferred like so: obj[Symbol.iterator]<T>(this: T).

Actual behavior: T cannot be inferred as the local this type, so all derivative types cannot be evaluated and the type stays as its generic version.

Playground Link

Related Issues: n/a

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:2
  • Comments:6 (1 by maintainers)

github_iconTop GitHub Comments

2reactions
Validarkcommented, Jun 21, 2020

I fixed it!! I’ll submit a PR soon! image

0reactions
greggmancommented, Dec 30, 2022

I think I ran into this issue recently. I had code like this

    [Symbol.iterator](): IterableIterator<CaptureState> {
        let i = 0;
        this.iterating = true;

        return {
            registry: this,
            next() {
                while (i < this.registry.objects.length) {
                    const obj = this.registry.objects[i++].deref();
                    if (obj === undefined) {
                        continue;
                    }
                    return { value: this.registry.get(obj), done: false };
                }
                this.registry.iterating = false;
                return { done: true };
            },
        };
    }

It complains about this.registry not existing but it does exist and the code actually runs fine. I worked around it by simplifying the code but was surprised TS lost track of this.

Read more comments on GitHub >

github_iconTop Results From Across the Web

TypeScript 2.8.3 Type must have a Symbol.iterator method ...
The error "Type Object must have a Symbol.iterator method that returns an iterator" occurs when we try to use the spread syntax (....
Read more >
TypeScript errors and how to fix them
error TS1337: An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.
Read more >
Generators and Iteration for ES5/ES3 - TypeScript 2.3 - 书栈网
This kind of iterator is useful for iterating over synchronously available values, such as the elements of an Array or the keys of...
Read more >
Overview - TypeScript
This is thanks to some changes in the Iterator and IteratorResult type ... During type argument inference in TypeScript 3.4, for a call...
Read more >
A tour of the Dart language
Unlike Java, Dart doesn't have the keywords public , protected , and private . ... The type of the name variable is inferred...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found