-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
IterableIterator<T>.next().value is now any instead of T #33353
Comments
The root cause is that That said: const iter = getIterator();
const result = iter.next();
if (!result.done) {
const value = result.value;
// value is a number here
} |
Okay, fair enough. I had been using I specifically ran into this with calling |
Yeah, it's unfortunate that |
Related issue: Turns out you can’t rely on the checking the |
Apparently this only happens when |
The problem was that we previously conflated the yielded type and the return type in generators. Unfortunately, defaulting to |
What about |
I have been using the following workaround so far: export interface StrictIterableIterator<T, TReturn> extends Iterator<T, TReturn> {
[Symbol.iterator](): StrictIterableIterator<T, TReturn>;
}
export interface StrictAsyncIterableIterator<T, TReturn> extends AsyncIterator<T, TReturn> {
[Symbol.asyncIterator](): StrictAsyncIterableIterator<T, TReturn>;
} |
Closing per above comment #33353 (comment) Requests for change in this behavior can be made in a new suggestion issue. Thanks! |
To others struggling with this:
If you're not using the return type (most iterators do not) the answer is This was quite frustrating and very unintuitive. I'd suggest you reopen and resolve this with a breaking change in the next major release. This is exactly the kind of drag that makes some people think TS is just a troublemaker that gets in the way of productivity. Iterators are common enough, and using return types is rare enough, that this ought to just work by default. The number of people who would be affected by changing the default return type to |
I welcome everyone. Yes, unfortunately, I also ran into a problem when I specify |
Unfortunately, const iterator: Iterator<number, never> = loadNumbers();
const next = iterator.next();
const str = next.value.toFixed() will not catch compilation error. Even in strict mode. Although this code: const iterator: Iterator<number, never> = loadNumbers();
const next = iterator.next();
if (next.done) {
const str = next.value.toFixed() // Error
} will result in a compilation error |
@mindplay-dk @SergeyDvornikovSetronica why not just use my solution above? |
@papb, unfortunately, this does not work, for exactly the same reason that is discussed everywhere. export interface StrictIterableIterator<T, TReturn> extends Iterator<T, TReturn> {
[Symbol.iterator](): StrictIterableIterator<T, TReturn>;
}
function* get(): StrictIterableIterator<number, void> {
yield 999;
}
const iterator = get();
const next = iterator.next();
if (!next.done) {
next.value.toFixed();
} leads to compilation error in ts 3.8.3 with error: test.ts:262:14 - error TS2339: Property 'toFixed' does not exist on type 'number | void'.
Property 'toFixed' does not exist on type 'void'.
262 next.value.toFixed();
~~~~~~~ even with strict mode disabled, although IDEA says everything is fine. Those. exactly what they say a lot where |
@SergeyDvornikovSetronica that's working as intended, as far as I can tell? If Here's an example to explain more clearly: function* loadNumbers(): Iterator<number, void> {
yield* [1,2,3];
}
const iterator = loadNumbers();
const next = iterator.next();
if (next.done) {
const str = next.value.toFixed() // 👍 Correctly errors (you're done - there is no value)
} else {
const str = next.value.toFixed() // 👍 Works
}
It was more verbose, and I don't need the result type, which I would never use. (I don't know why it exist in JS in the first place - a function should either return or yield, doing both has really confusing ergonomics.) Simply type-hinting like I did in the example here has been working fine for me. The remaining question is why does this default to I think this issue should be reopened. |
Yes, const iterator: Iterator<number, never> = loadNumbers();
const next = iterator.next();
if (next.done) {
const str = next.value.toFixed() // Error
} works as intended, but your example: function* loadNumbers(): Iterator<number, void> {
yield* [1,2,3];
}
const iterator = loadNumbers();
const next = iterator.next();
if (next.done) {
const str = next.value.toFixed() // 👍 Correctly errors (you're done - there is no value)
} else {
const str = next.value.toFixed() // 👍 Works <-- Error
} not working (for me, on ts 3.8.3) with error error TS2339: Property 'toFixed' does not exist on type 'number | void'.
Property 'toFixed' does not exist on type 'void'.
491 const str = next.value.toFixed() // 👍 Works although IDEA doesn't highlight any errors |
As I said before, function* loadNumbers(): Iterator<number, never> {
yield* [1, 2, 3];
return undefined as never;
}
const iterator = loadNumbers();
const next = iterator.next();
const str = next.value.toFixed() // 👍 Works compiles without problems even though it is an error |
@SergeyDvornikovSetronica I think the assumption with these type definitions is you're going to check So this doesn't force you to correctly write manual iteration code - that may not have been the primary goal with these types, or it could be that this was just written before TS 3.5 added smarter union type checking. Arguably, maybe the underlying type union should have been the "reverse" somehow, forcing you to "prove" that there is a Here's an example of forcing a caller to check a type union: type TResultDone<T> = { done: true }; // { done: true, value: void } would also work (but value: never would not)
type TResultNotDone<T> = { done: false, value: T };
type TResult<T> = TResultDone<T> | TResultNotDone<T>;
function maybeNumber(): TResult<number> {
return { done: false, value: 1 };
}
const result = maybeNumber();
const a = result.value.toFixed(); // 👍 correctly fails: you must check done first
if (result.done) {
const b = result.value.toFixed(); // 👍 correctly fails: we're done, there is no value
} else {
const c = result.value.toFixed(); // 👍 succeeds: we're not done, so there is a value
} As noted in the comment, a union with I haven't looked closely, but this should be applicable to solving this Iterator problem. I'm sure more improvements could be made here, which is why I'm calling for this issue to be reopened. You did mention you're using TS 3.8.3, which is very old - I don't know which other problems it might have, but I did check my last example here with that version, and it worked fine. Maybe consider upgrading to TS 4. You also mentioned IDEA doesn't highlight errors - that's probably because you're using the built-in TS 4 version - to get correct error messages in IDEA (or VS Code or most other IDEs) you need to configure the IDE to use your local TS version instead. |
This is pretty common when you want to get the first element from an iterable that doesn't otherwise provide direct access (e.g. a
I don't think this is true for any version of TypeScript. The union between |
Shouldn't this be reopened? Now that Iterator Helpers is going to be a thing in browsers, generators are going to be used a lot more outside of |
This issue should be reopened. The type gymnastics required here are fairly extreme, and needlessly so. Also, the solution to achieve the expected behavior is too obstruse -- users should not be expected to know in advance any special knowledge required to achieve the expected result here. The way this works should be redesigned with these points in mind. |
No matter what changes we might make here, the only safe way to access In #58243 we are looking into adding the We're also looking to explicitly type built-ins to use |
So is this typescript-approved way to fetch the first value of a set? const inputs = ["typescript"] as const;
const setExample: ReadonlySet<(typeof inputs)[number]> = new Set(inputs);
const step = setExample.keys().next();
if (!step.done) {
const value = step.value;
} else {
const value = step.value;
} Can't even use |
The function getValue<T, TReturn>(res: IteratorResult<T, TReturn>): T {
if (res.done) {
throw new Error("Iterator was closed");
}
return res.value;
} and then you can use It's important to remember that the compiler can't make as many assumptions about iterators as it can about a tuple, so |
You could also use this code to retrieve the first value: const [value] = setExample.keys() |
TypeScript Version: 3.6.2
Search Terms: iterableiterator iterator
Code
Expected behavior:
value: number
(TS 3.5)Actual behavior:
value: any
Playground Link:
NOTE: Playground is running TS 3.5 and exhibits "Expected behavior"
http://www.typescriptlang.org/play/#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwHMQMBJDEGKDHGACgEoAueMiqAIwhFcupgB5UyALbsKAPgDcAKDB4AzhnhZyMeAF5CxHlRoNZCpQG94ANygRkCAL4blqgHSoQADwz6gA
Related Issues:
The text was updated successfully, but these errors were encountered: