Skip to content

Commit e8bf958

Browse files
authored
Improve type checking and inference for Generators and Async Generators (microsoft#30790)
* Improve typing for Generators and Async Generators * Add TReturn and TNext to Iterator, IterableIterator, etc. * Update ts internal Iterator to be assignable from global Iterator * Make 'done' optional in IteratorYieldResult * Revert Iterable and IterableIterator to simpler versions plus other fixes * Add additional inference tests * Added additional tests * PR cleanup and minor async iteration type fix * Updated diagnostics message and added non-strict tests * Fix expected arity of Iterator/AsyncIterator
1 parent 0bea4bd commit e8bf958

File tree

255 files changed

+4887
-1509
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

255 files changed

+4887
-1509
lines changed

src/compiler/builderState.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,8 @@ namespace ts.BuilderState {
427427
const references = state.referencedMap.get(path);
428428
if (references) {
429429
const iterator = references.keys();
430-
for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) {
431-
queue.push(value as Path);
430+
for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) {
431+
queue.push(iterResult.value as Path);
432432
}
433433
}
434434
}

src/compiler/checker.ts

+831-321
Large diffs are not rendered by default.

src/compiler/commandLineParser.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ namespace ts {
4040
["es2017.string", "lib.es2017.string.d.ts"],
4141
["es2017.intl", "lib.es2017.intl.d.ts"],
4242
["es2017.typedarrays", "lib.es2017.typedarrays.d.ts"],
43+
["es2018.asyncgenerator", "lib.es2018.asyncgenerator.d.ts"],
4344
["es2018.asynciterable", "lib.es2018.asynciterable.d.ts"],
4445
["es2018.intl", "lib.es2018.intl.d.ts"],
4546
["es2018.promise", "lib.es2018.promise.d.ts"],
@@ -1899,7 +1900,9 @@ namespace ts {
18991900
case "object":
19001901
return {};
19011902
default:
1902-
return option.type.keys().next().value;
1903+
const iterResult = option.type.keys().next();
1904+
if (!iterResult.done) return iterResult.value;
1905+
return Debug.fail("Expected 'option.type' to have entries.");
19031906
}
19041907
}
19051908

src/compiler/core.ts

+15-14
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ namespace ts {
2424
" __sortedArrayBrand": any;
2525
}
2626

27-
2827
/** ES6 Map interface, only read methods included. */
2928
export interface ReadonlyMap<T> {
3029
get(key: string): T | undefined;
@@ -45,7 +44,7 @@ namespace ts {
4544

4645
/** ES6 Iterator type. */
4746
export interface Iterator<T> {
48-
next(): { value: T, done: false } | { value: never, done: true };
47+
next(): { value: T, done?: false } | { value: never, done: true };
4948
}
5049

5150
/** Array that is only intended to be pushed to, never read. */
@@ -297,12 +296,13 @@ namespace ts {
297296
forEach(action: (value: T, key: string) => void): void {
298297
const iterator = this.entries();
299298
while (true) {
300-
const { value: entry, done } = iterator.next();
301-
if (done) {
299+
const iterResult = iterator.next();
300+
if (iterResult.done) {
302301
break;
303302
}
304303

305-
action(entry[1], entry[0]);
304+
const [key, value] = iterResult.value;
305+
action(value, key);
306306
}
307307
}
308308
};
@@ -346,11 +346,11 @@ namespace ts {
346346

347347
export function firstDefinedIterator<T, U>(iter: Iterator<T>, callback: (element: T) => U | undefined): U | undefined {
348348
while (true) {
349-
const { value, done } = iter.next();
350-
if (done) {
349+
const iterResult = iter.next();
350+
if (iterResult.done) {
351351
return undefined;
352352
}
353-
const result = callback(value);
353+
const result = callback(iterResult.value);
354354
if (result !== undefined) {
355355
return result;
356356
}
@@ -375,7 +375,7 @@ namespace ts {
375375
return { value: undefined as never, done: true };
376376
}
377377
i++;
378-
return { value: [arrayA[i - 1], arrayB[i - 1]], done: false };
378+
return { value: [arrayA[i - 1], arrayB[i - 1]] as [T, U], done: false };
379379
}
380380
};
381381
}
@@ -567,7 +567,7 @@ namespace ts {
567567
return {
568568
next() {
569569
const iterRes = iter.next();
570-
return iterRes.done ? iterRes : { value: mapFn(iterRes.value), done: false };
570+
return iterRes.done ? iterRes as { done: true, value: never } : { value: mapFn(iterRes.value), done: false };
571571
}
572572
};
573573
}
@@ -678,7 +678,7 @@ namespace ts {
678678
}
679679
const iterRes = iter.next();
680680
if (iterRes.done) {
681-
return iterRes;
681+
return iterRes as { done: true, value: never };
682682
}
683683
currentIter = getIterator(iterRes.value);
684684
}
@@ -753,7 +753,7 @@ namespace ts {
753753
while (true) {
754754
const res = iter.next();
755755
if (res.done) {
756-
return res;
756+
return res as { done: true, value: never };
757757
}
758758
const value = mapFn(res.value);
759759
if (value !== undefined) {
@@ -1078,6 +1078,7 @@ namespace ts {
10781078
* @param value The value to append to the array. If `value` is `undefined`, nothing is
10791079
* appended.
10801080
*/
1081+
export function append<TArray extends any[] | undefined, TValue extends NonNullable<TArray>[number] | undefined>(to: TArray, value: TValue): [undefined, undefined] extends [TArray, TValue] ? TArray : NonNullable<TArray>[number][];
10811082
export function append<T>(to: T[], value: T | undefined): T[];
10821083
export function append<T>(to: T[] | undefined, value: T): T[];
10831084
export function append<T>(to: T[] | undefined, value: T | undefined): T[] | undefined;
@@ -1402,8 +1403,8 @@ namespace ts {
14021403
export function arrayFrom<T>(iterator: Iterator<T> | IterableIterator<T>): T[];
14031404
export function arrayFrom<T, U>(iterator: Iterator<T> | IterableIterator<T>, map?: (t: T) => U): (T | U)[] {
14041405
const result: (T | U)[] = [];
1405-
for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) {
1406-
result.push(map ? map(value) : value);
1406+
for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) {
1407+
result.push(map ? map(iterResult.value) : iterResult.value);
14071408
}
14081409
return result;
14091410
}

src/compiler/diagnosticMessages.json

+32-3
Original file line numberDiff line numberDiff line change
@@ -1764,7 +1764,7 @@
17641764
"category": "Error",
17651765
"code": 2489
17661766
},
1767-
"The type returned by the 'next()' method of an iterator must have a 'value' property.": {
1767+
"The type returned by the '{0}()' method of an iterator must have a 'value' property.": {
17681768
"category": "Error",
17691769
"code": 2490
17701770
},
@@ -1992,7 +1992,7 @@
19921992
"category": "Error",
19931993
"code": 2546
19941994
},
1995-
"The type returned by the 'next()' method of an async iterator must be a promise for a type with a 'value' property.": {
1995+
"The type returned by the '{0}()' method of an async iterator must be a promise for a type with a 'value' property.": {
19961996
"category": "Error",
19971997
"code": 2547
19981998
},
@@ -2653,6 +2653,30 @@
26532653
"category": "Error",
26542654
"code": 2762
26552655
},
2656+
"Cannot iterate value because the 'next' method of its iterator expects type '{1}', but for-of will always send '{0}'.": {
2657+
"category": "Error",
2658+
"code": 2763
2659+
},
2660+
"Cannot iterate value because the 'next' method of its iterator expects type '{1}', but array spread will always send '{0}'.": {
2661+
"category": "Error",
2662+
"code": 2764
2663+
},
2664+
"Cannot iterate value because the 'next' method of its iterator expects type '{1}', but array destructuring will always send '{0}'.": {
2665+
"category": "Error",
2666+
"code": 2765
2667+
},
2668+
"Cannot delegate iteration to value because the 'next' method of its iterator expects type '{1}', but the containing generator will always send '{0}'.": {
2669+
"category": "Error",
2670+
"code": 2766
2671+
},
2672+
"The '{0}' property of an iterator must be a method.": {
2673+
"category": "Error",
2674+
"code": 2767
2675+
},
2676+
"The '{0}' property of an async iterator must be a method.": {
2677+
"category": "Error",
2678+
"code": 2768
2679+
},
26562680

26572681
"Import declaration '{0}' is using private name '{1}'.": {
26582682
"category": "Error",
@@ -4214,7 +4238,7 @@
42144238
"category": "Error",
42154239
"code": 7024
42164240
},
4217-
"Generator implicitly has type '{0}' because it does not yield any values. Consider supplying a return type.": {
4241+
"Generator implicitly has yield type '{0}' because it does not yield any values. Consider supplying a return type annotation.": {
42184242
"category": "Error",
42194243
"code": 7025
42204244
},
@@ -4336,6 +4360,11 @@
43364360
"category": "Error",
43374361
"code": 7054
43384362
},
4363+
"'{0}', which lacks return-type annotation, implicitly has an '{1}' yield type.": {
4364+
"category": "Error",
4365+
"code": 7055
4366+
},
4367+
43394368
"You cannot rename this element.": {
43404369
"category": "Error",
43414370
"code": 8000

src/compiler/sourcemap.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ namespace ts {
148148
const sourceIndexToNewSourceIndexMap: number[] = [];
149149
let nameIndexToNewNameIndexMap: number[] | undefined;
150150
const mappingIterator = decodeMappings(map.mappings);
151-
for (let { value: raw, done } = mappingIterator.next(); !done; { value: raw, done } = mappingIterator.next()) {
151+
for (let iterResult = mappingIterator.next(); !iterResult.done; iterResult = mappingIterator.next()) {
152+
const raw = iterResult.value;
152153
if (end && (
153154
raw.generatedLine > end.line ||
154155
(raw.generatedLine === end.line && raw.generatedCharacter > end.character))) {

src/compiler/types.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -4273,13 +4273,23 @@ namespace ts {
42734273
regularType: ResolvedType; // Regular version of fresh type
42744274
}
42754275

4276+
/* @internal */
4277+
export interface IterationTypes {
4278+
readonly yieldType: Type;
4279+
readonly returnType: Type;
4280+
readonly nextType: Type;
4281+
}
4282+
42764283
// Just a place to cache element types of iterables and iterators
42774284
/* @internal */
42784285
export interface IterableOrIteratorType extends ObjectType, UnionType {
4279-
iteratedTypeOfIterable?: Type;
4280-
iteratedTypeOfIterator?: Type;
4281-
iteratedTypeOfAsyncIterable?: Type;
4282-
iteratedTypeOfAsyncIterator?: Type;
4286+
iterationTypesOfGeneratorReturnType?: IterationTypes;
4287+
iterationTypesOfAsyncGeneratorReturnType?: IterationTypes;
4288+
iterationTypesOfIterable?: IterationTypes;
4289+
iterationTypesOfIterator?: IterationTypes;
4290+
iterationTypesOfAsyncIterable?: IterationTypes;
4291+
iterationTypesOfAsyncIterator?: IterationTypes;
4292+
iterationTypesOfIteratorResult?: IterationTypes;
42834293
}
42844294

42854295
/* @internal */

src/compiler/utilities.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ namespace ts {
150150
export function forEachEntry<T, U>(map: ReadonlyMap<T>, callback: (value: T, key: string) => U | undefined): U | undefined;
151151
export function forEachEntry<T, U>(map: ReadonlyUnderscoreEscapedMap<T> | ReadonlyMap<T>, callback: (value: T, key: (string & __String)) => U | undefined): U | undefined {
152152
const iterator = map.entries();
153-
for (let { value: pair, done } = iterator.next(); !done; { value: pair, done } = iterator.next()) {
154-
const [key, value] = pair;
153+
for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) {
154+
const [key, value] = iterResult.value;
155155
const result = callback(value, key as (string & __String));
156156
if (result) {
157157
return result;
@@ -165,8 +165,8 @@ namespace ts {
165165
export function forEachKey<T>(map: ReadonlyMap<{}>, callback: (key: string) => T | undefined): T | undefined;
166166
export function forEachKey<T>(map: ReadonlyUnderscoreEscapedMap<{}> | ReadonlyMap<{}>, callback: (key: string & __String) => T | undefined): T | undefined {
167167
const iterator = map.keys();
168-
for (let { value: key, done } = iterator.next(); !done; { value: key, done } = iterator.next()) {
169-
const result = callback(key as string & __String);
168+
for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) {
169+
const result = callback(iterResult.value as string & __String);
170170
if (result) {
171171
return result;
172172
}

src/harness/sourceMapRecorder.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,8 @@ namespace Harness.SourceMapRecorder {
301301

302302
SourceMapSpanWriter.initializeSourceMapSpanWriter(sourceMapRecorder, sourceMapData.sourceMap, currentFile);
303303
const mapper = ts.decodeMappings(sourceMapData.sourceMap.mappings);
304-
for (let { value: decodedSourceMapping, done } = mapper.next(); !done; { value: decodedSourceMapping, done } = mapper.next()) {
304+
for (let iterResult = mapper.next(); !iterResult.done; iterResult = mapper.next()) {
305+
const decodedSourceMapping = iterResult.value;
305306
const currentSourceFile = ts.isSourceMapping(decodedSourceMapping)
306307
? program.getSourceFile(sourceMapData.inputSourceFileNames[decodedSourceMapping.sourceIndex])
307308
: undefined;
@@ -335,7 +336,8 @@ namespace Harness.SourceMapRecorder {
335336

336337
SourceMapSpanWriter.initializeSourceMapSpanWriter(sourceMapRecorder, sourceMap, currentFile);
337338
const mapper = ts.decodeMappings(sourceMap.mappings);
338-
for (let { value: decodedSourceMapping, done } = mapper.next(); !done; { value: decodedSourceMapping, done } = mapper.next()) {
339+
for (let iterResult = mapper.next(); !iterResult.done; iterResult = mapper.next()) {
340+
const decodedSourceMapping = iterResult.value;
339341
const currentSourceFile = ts.isSourceMapping(decodedSourceMapping)
340342
? getFile(sourceFileAbsolutePaths[decodedSourceMapping.sourceIndex])
341343
: undefined;

src/harness/vfs.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@ namespace vfs {
683683

684684
if (isDirectory(node)) throw createIOError("EISDIR");
685685
if (!isFile(node)) throw createIOError("EBADF");
686-
node.buffer = Buffer.isBuffer(data) ? data.slice() : ts.sys.bufferFrom!("" + data, encoding || "utf8");
686+
node.buffer = Buffer.isBuffer(data) ? data.slice() : ts.sys.bufferFrom!("" + data, encoding || "utf8") as Buffer;
687687
node.size = node.buffer.byteLength;
688688
node.mtimeMs = time;
689689
node.ctimeMs = time;
@@ -1204,7 +1204,7 @@ namespace vfs {
12041204
}
12051205
},
12061206
readFileSync(path: string): Buffer {
1207-
return ts.sys.bufferFrom!(host.readFile(path)!, "utf8"); // TODO: GH#18217
1207+
return ts.sys.bufferFrom!(host.readFile(path)!, "utf8") as Buffer; // TODO: GH#18217
12081208
}
12091209
};
12101210
}

src/lib/es2015.generator.d.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
interface Generator extends Iterator<any> { }
1+
/// <reference lib="es2015.iterable" />
2+
3+
interface Generator<T = unknown, TReturn = any, TNext = unknown> extends Iterator<T, TReturn, TNext> {
4+
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
5+
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
6+
return(value: TReturn): IteratorResult<T, TReturn>;
7+
throw(e: any): IteratorResult<T, TReturn>;
8+
[Symbol.iterator](): Generator<T, TReturn, TNext>;
9+
}
210

311
interface GeneratorFunction {
412
/**

src/lib/es2015.iterable.d.ts

+15-7
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,23 @@ interface SymbolConstructor {
88
readonly iterator: symbol;
99
}
1010

11-
interface IteratorResult<T> {
12-
done: boolean;
13-
value: T;
11+
interface IteratorYieldResult<TYield> {
12+
done?: false;
13+
value: TYield;
1414
}
1515

16-
interface Iterator<T> {
17-
next(value?: any): IteratorResult<T>;
18-
return?(value?: any): IteratorResult<T>;
19-
throw?(e?: any): IteratorResult<T>;
16+
interface IteratorReturnResult<TReturn> {
17+
done: true;
18+
value: TReturn;
19+
}
20+
21+
type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
22+
23+
interface Iterator<T, TReturn = any, TNext = undefined> {
24+
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
25+
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
26+
return?(value?: TReturn): IteratorResult<T, TReturn>;
27+
throw?(e?: any): IteratorResult<T, TReturn>;
2028
}
2129

2230
interface Iterable<T> {

0 commit comments

Comments
 (0)