Skip to content

Commit 68ba670

Browse files
authored
Add contextual type for generator return type (#39772)
* WIP * Add contextual type for generator return type
1 parent 5b2b70b commit 68ba670

12 files changed

+284
-12
lines changed

src/compiler/checker.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22977,18 +22977,25 @@ namespace ts {
2297722977
function getContextualTypeForReturnExpression(node: Expression): Type | undefined {
2297822978
const func = getContainingFunction(node);
2297922979
if (func) {
22980-
const functionFlags = getFunctionFlags(func);
22981-
if (functionFlags & FunctionFlags.Generator) { // AsyncGenerator function or Generator function
22982-
return undefined;
22983-
}
22984-
22985-
const contextualReturnType = getContextualReturnType(func);
22980+
let contextualReturnType = getContextualReturnType(func);
2298622981
if (contextualReturnType) {
22987-
if (functionFlags & FunctionFlags.Async) { // Async function
22988-
const contextualAwaitedType = mapType(contextualReturnType, getAwaitedTypeOfPromise);
22982+
const functionFlags = getFunctionFlags(func);
22983+
if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function
22984+
const use = functionFlags & FunctionFlags.Async ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType;
22985+
const iterationTypes = getIterationTypesOfIterable(contextualReturnType, use, /*errorNode*/ undefined);
22986+
if (!iterationTypes) {
22987+
return undefined;
22988+
}
22989+
contextualReturnType = iterationTypes.returnType;
22990+
// falls through to unwrap Promise for AsyncGenerators
22991+
}
22992+
22993+
if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function
22994+
const contextualAwaitedType = mapType(contextualReturnType, getAwaitedType);
2298922995
return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]);
2299022996
}
22991-
return contextualReturnType; // Regular function
22997+
22998+
return contextualReturnType; // Regular function or Generator function
2299222999
}
2299323000
}
2299423001
return undefined;
@@ -33439,6 +33446,8 @@ namespace ts {
3343933446
reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag));
3344033447
errorNode = undefined;
3344133448
}
33449+
setCachedIterationTypes(type, cacheKey, noIterationTypes);
33450+
return undefined;
3344233451
}
3344333452
else {
3344433453
allIterationTypes = append(allIterationTypes, iterationTypes);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment4.ts(5,7): error TS2548: Type 'number[] | null' is not an array type or does not have a '[Symbol.iterator]()' method that returns an iterator.
2+
3+
4+
==== tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment4.ts (1 errors) ====
5+
// #35497
6+
7+
8+
declare const data: number[] | null;
9+
const [value] = data; // Error
10+
~~~~~~~
11+
!!! error TS2548: Type 'number[] | null' is not an array type or does not have a '[Symbol.iterator]()' method that returns an iterator.
12+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//// [destructuringArrayBindingPatternAndAssignment4.ts]
2+
// #35497
3+
4+
5+
declare const data: number[] | null;
6+
const [value] = data; // Error
7+
8+
9+
//// [destructuringArrayBindingPatternAndAssignment4.js]
10+
"use strict";
11+
// #35497
12+
var __read = (this && this.__read) || function (o, n) {
13+
var m = typeof Symbol === "function" && o[Symbol.iterator];
14+
if (!m) return o;
15+
var i = m.call(o), r, ar = [], e;
16+
try {
17+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
18+
}
19+
catch (error) { e = { error: error }; }
20+
finally {
21+
try {
22+
if (r && !r.done && (m = i["return"])) m.call(i);
23+
}
24+
finally { if (e) throw e.error; }
25+
}
26+
return ar;
27+
};
28+
var _a = __read(data, 1), value = _a[0]; // Error
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
=== tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment4.ts ===
2+
// #35497
3+
4+
5+
declare const data: number[] | null;
6+
>data : Symbol(data, Decl(destructuringArrayBindingPatternAndAssignment4.ts, 3, 13))
7+
8+
const [value] = data; // Error
9+
>value : Symbol(value, Decl(destructuringArrayBindingPatternAndAssignment4.ts, 4, 7))
10+
>data : Symbol(data, Decl(destructuringArrayBindingPatternAndAssignment4.ts, 3, 13))
11+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment4.ts ===
2+
// #35497
3+
4+
5+
declare const data: number[] | null;
6+
>data : number[] | null
7+
>null : null
8+
9+
const [value] = data; // Error
10+
>value : any
11+
>data : number[] | null
12+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
tests/cases/conformance/generators/generatorReturnContextualType.ts(17,3): error TS2322: Type '{ x: string; }' is not assignable to type '{ x: "x"; }'.
2+
Types of property 'x' are incompatible.
3+
Type 'string' is not assignable to type '"x"'.
4+
5+
6+
==== tests/cases/conformance/generators/generatorReturnContextualType.ts (1 errors) ====
7+
// #35995
8+
9+
function* f1(): Generator<any, { x: 'x' }, any> {
10+
return { x: 'x' };
11+
}
12+
13+
async function* f2(): AsyncGenerator<any, { x: 'x' }, any> {
14+
return { x: 'x' };
15+
}
16+
17+
async function* f3(): AsyncGenerator<any, { x: 'x' }, any> {
18+
return Promise.resolve({ x: 'x' });
19+
}
20+
21+
async function* f4(): AsyncGenerator<any, { x: 'x' }, any> {
22+
const ret = { x: 'x' };
23+
return Promise.resolve(ret); // Error
24+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25+
!!! error TS2322: Type '{ x: string; }' is not assignable to type '{ x: "x"; }'.
26+
!!! error TS2322: Types of property 'x' are incompatible.
27+
!!! error TS2322: Type 'string' is not assignable to type '"x"'.
28+
}
29+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//// [generatorReturnContextualType.ts]
2+
// #35995
3+
4+
function* f1(): Generator<any, { x: 'x' }, any> {
5+
return { x: 'x' };
6+
}
7+
8+
async function* f2(): AsyncGenerator<any, { x: 'x' }, any> {
9+
return { x: 'x' };
10+
}
11+
12+
async function* f3(): AsyncGenerator<any, { x: 'x' }, any> {
13+
return Promise.resolve({ x: 'x' });
14+
}
15+
16+
async function* f4(): AsyncGenerator<any, { x: 'x' }, any> {
17+
const ret = { x: 'x' };
18+
return Promise.resolve(ret); // Error
19+
}
20+
21+
22+
//// [generatorReturnContextualType.js]
23+
"use strict";
24+
// #35995
25+
function* f1() {
26+
return { x: 'x' };
27+
}
28+
async function* f2() {
29+
return { x: 'x' };
30+
}
31+
async function* f3() {
32+
return Promise.resolve({ x: 'x' });
33+
}
34+
async function* f4() {
35+
const ret = { x: 'x' };
36+
return Promise.resolve(ret); // Error
37+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
=== tests/cases/conformance/generators/generatorReturnContextualType.ts ===
2+
// #35995
3+
4+
function* f1(): Generator<any, { x: 'x' }, any> {
5+
>f1 : Symbol(f1, Decl(generatorReturnContextualType.ts, 0, 0))
6+
>Generator : Symbol(Generator, Decl(lib.es2015.generator.d.ts, --, --))
7+
>x : Symbol(x, Decl(generatorReturnContextualType.ts, 2, 32))
8+
9+
return { x: 'x' };
10+
>x : Symbol(x, Decl(generatorReturnContextualType.ts, 3, 10))
11+
}
12+
13+
async function* f2(): AsyncGenerator<any, { x: 'x' }, any> {
14+
>f2 : Symbol(f2, Decl(generatorReturnContextualType.ts, 4, 1))
15+
>AsyncGenerator : Symbol(AsyncGenerator, Decl(lib.es2018.asyncgenerator.d.ts, --, --))
16+
>x : Symbol(x, Decl(generatorReturnContextualType.ts, 6, 43))
17+
18+
return { x: 'x' };
19+
>x : Symbol(x, Decl(generatorReturnContextualType.ts, 7, 10))
20+
}
21+
22+
async function* f3(): AsyncGenerator<any, { x: 'x' }, any> {
23+
>f3 : Symbol(f3, Decl(generatorReturnContextualType.ts, 8, 1))
24+
>AsyncGenerator : Symbol(AsyncGenerator, Decl(lib.es2018.asyncgenerator.d.ts, --, --))
25+
>x : Symbol(x, Decl(generatorReturnContextualType.ts, 10, 43))
26+
27+
return Promise.resolve({ x: 'x' });
28+
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
29+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
30+
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
31+
>x : Symbol(x, Decl(generatorReturnContextualType.ts, 11, 26))
32+
}
33+
34+
async function* f4(): AsyncGenerator<any, { x: 'x' }, any> {
35+
>f4 : Symbol(f4, Decl(generatorReturnContextualType.ts, 12, 1))
36+
>AsyncGenerator : Symbol(AsyncGenerator, Decl(lib.es2018.asyncgenerator.d.ts, --, --))
37+
>x : Symbol(x, Decl(generatorReturnContextualType.ts, 14, 43))
38+
39+
const ret = { x: 'x' };
40+
>ret : Symbol(ret, Decl(generatorReturnContextualType.ts, 15, 7))
41+
>x : Symbol(x, Decl(generatorReturnContextualType.ts, 15, 15))
42+
43+
return Promise.resolve(ret); // Error
44+
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
45+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
46+
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
47+
>ret : Symbol(ret, Decl(generatorReturnContextualType.ts, 15, 7))
48+
}
49+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
=== tests/cases/conformance/generators/generatorReturnContextualType.ts ===
2+
// #35995
3+
4+
function* f1(): Generator<any, { x: 'x' }, any> {
5+
>f1 : () => Generator<any, { x: 'x';}, any>
6+
>x : "x"
7+
8+
return { x: 'x' };
9+
>{ x: 'x' } : { x: "x"; }
10+
>x : "x"
11+
>'x' : "x"
12+
}
13+
14+
async function* f2(): AsyncGenerator<any, { x: 'x' }, any> {
15+
>f2 : () => AsyncGenerator<any, { x: 'x';}, any>
16+
>x : "x"
17+
18+
return { x: 'x' };
19+
>{ x: 'x' } : { x: "x"; }
20+
>x : "x"
21+
>'x' : "x"
22+
}
23+
24+
async function* f3(): AsyncGenerator<any, { x: 'x' }, any> {
25+
>f3 : () => AsyncGenerator<any, { x: 'x';}, any>
26+
>x : "x"
27+
28+
return Promise.resolve({ x: 'x' });
29+
>Promise.resolve({ x: 'x' }) : Promise<{ x: "x"; }>
30+
>Promise.resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
31+
>Promise : PromiseConstructor
32+
>resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
33+
>{ x: 'x' } : { x: "x"; }
34+
>x : "x"
35+
>'x' : "x"
36+
}
37+
38+
async function* f4(): AsyncGenerator<any, { x: 'x' }, any> {
39+
>f4 : () => AsyncGenerator<any, { x: 'x';}, any>
40+
>x : "x"
41+
42+
const ret = { x: 'x' };
43+
>ret : { x: string; }
44+
>{ x: 'x' } : { x: string; }
45+
>x : string
46+
>'x' : "x"
47+
48+
return Promise.resolve(ret); // Error
49+
>Promise.resolve(ret) : Promise<{ x: string; }>
50+
>Promise.resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
51+
>Promise : PromiseConstructor
52+
>resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
53+
>ret : { x: string; }
54+
}
55+

tests/baselines/reference/generatorYieldContextualType.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ declare function f2<T, R, S>(gen: () => Generator<R, T, S> | AsyncGenerator<R, T
2525
f2<0, 0, 1>(async function* () {
2626
>f2<0, 0, 1>(async function* () { const a = yield 0; return 0;}) : void
2727
>f2 : <T, R, S>(gen: () => Generator<R, T, S> | AsyncGenerator<R, T, S>) => void
28-
>async function* () { const a = yield 0; return 0;} : () => AsyncGenerator<0, 0, 1>
28+
>async function* () { const a = yield 0; return 0;} : () => AsyncGenerator<0, 0, 1 | undefined>
2929

3030
const a = yield 0;
31-
>a : 1
32-
>yield 0 : 1
31+
>a : 1 | undefined
32+
>yield 0 : 1 | undefined
3333
>0 : 0
3434

3535
return 0;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// #35497
2+
3+
// @target: es5
4+
// @downlevelIteration: true
5+
// @lib: es6
6+
// @strict: true
7+
8+
declare const data: number[] | null;
9+
const [value] = data; // Error
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// @target: esnext
2+
// @strict: true
3+
4+
// #35995
5+
6+
function* f1(): Generator<any, { x: 'x' }, any> {
7+
return { x: 'x' };
8+
}
9+
10+
async function* f2(): AsyncGenerator<any, { x: 'x' }, any> {
11+
return { x: 'x' };
12+
}
13+
14+
async function* f3(): AsyncGenerator<any, { x: 'x' }, any> {
15+
return Promise.resolve({ x: 'x' });
16+
}
17+
18+
async function* f4(): AsyncGenerator<any, { x: 'x' }, any> {
19+
const ret = { x: 'x' };
20+
return Promise.resolve(ret); // Error
21+
}

0 commit comments

Comments
 (0)