Skip to content

Commit 1c49a8b

Browse files
authored
Filter possible contextual return types from unions for async functions and generators (#51196)
1 parent 41ebfbf commit 1c49a8b

23 files changed

+2059
-22
lines changed

src/compiler/checker.ts

+33-13
Original file line numberDiff line numberDiff line change
@@ -29777,7 +29777,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2977729777
// and that call signature is non-generic, return statements are contextually typed by the return type of the signature
2977829778
const signature = getContextualSignatureForFunctionLikeDeclaration(functionDecl as FunctionExpression);
2977929779
if (signature && !isResolvingReturnTypeOfSignature(signature)) {
29780-
return getReturnTypeOfSignature(signature);
29780+
const returnType = getReturnTypeOfSignature(signature);
29781+
const functionFlags = getFunctionFlags(functionDecl);
29782+
if (functionFlags & FunctionFlags.Generator) {
29783+
return filterType(returnType, t => {
29784+
return !!(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.InstantiableNonPrimitive)) || checkGeneratorInstantiationAssignabilityToReturnType(t, functionFlags, /*errorNode*/ undefined);
29785+
});
29786+
}
29787+
if (functionFlags & FunctionFlags.Async) {
29788+
return filterType(returnType, t => {
29789+
return !!(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.InstantiableNonPrimitive)) || !!getAwaitedTypeOfPromise(t);
29790+
});
29791+
}
29792+
return returnType;
2978129793
}
2978229794
const iife = getImmediatelyInvokedFunctionExpression(functionDecl);
2978329795
if (iife) {
@@ -38359,7 +38371,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3835938371
// There is no point in doing an assignability check if the function
3836038372
// has no explicit return type because the return type is directly computed
3836138373
// from the yield expressions.
38362-
const returnType = getReturnTypeFromAnnotation(func);
38374+
let returnType = getReturnTypeFromAnnotation(func);
38375+
if (returnType && returnType.flags & TypeFlags.Union) {
38376+
returnType = filterType(returnType, t => checkGeneratorInstantiationAssignabilityToReturnType(t, functionFlags, /*errorNode*/ undefined));
38377+
}
3836338378
const iterationTypes = returnType && getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsync);
3836438379
const signatureYieldType = iterationTypes && iterationTypes.yieldType || anyType;
3836538380
const signatureNextType = iterationTypes && iterationTypes.nextType || anyType;
@@ -39287,17 +39302,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3928739302
error(returnTypeErrorLocation, Diagnostics.A_generator_cannot_have_a_void_type_annotation);
3928839303
}
3928939304
else {
39290-
// Naively, one could check that Generator<any, any, any> is assignable to the return type annotation.
39291-
// However, that would not catch the error in the following case.
39292-
//
39293-
// interface BadGenerator extends Iterable<number>, Iterator<string> { }
39294-
// function* g(): BadGenerator { } // Iterable and Iterator have different types!
39295-
//
39296-
const generatorYieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, (functionFlags & FunctionFlags.Async) !== 0) || anyType;
39297-
const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType;
39298-
const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType;
39299-
const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async));
39300-
checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeErrorLocation);
39305+
checkGeneratorInstantiationAssignabilityToReturnType(returnType, functionFlags, returnTypeErrorLocation);
3930139306
}
3930239307
}
3930339308
else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) {
@@ -39310,6 +39315,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3931039315
}
3931139316
}
3931239317

39318+
function checkGeneratorInstantiationAssignabilityToReturnType(returnType: Type, functionFlags: FunctionFlags, errorNode?: TypeNode) {
39319+
// Naively, one could check that Generator<any, any, any> is assignable to the return type annotation.
39320+
// However, that would not catch the error in the following case.
39321+
//
39322+
// interface BadGenerator extends Iterable<number>, Iterator<string> { }
39323+
// function* g(): BadGenerator { } // Iterable and Iterator have different types!
39324+
//
39325+
const generatorYieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, (functionFlags & FunctionFlags.Async) !== 0) || anyType;
39326+
const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType;
39327+
const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType;
39328+
const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async));
39329+
39330+
return checkTypeAssignableTo(generatorInstantiation, returnType, errorNode);
39331+
}
39332+
3931339333
function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) {
3931439334
const instanceNames = new Map<__String, DeclarationMeaning>();
3931539335
const staticNames = new Map<__String, DeclarationMeaning>();

tests/baselines/reference/contextualTypeOnYield1.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ type FuncOrGeneratorFunc = () => (number | Generator<(arg: number) => void, any,
77

88
const f: FuncOrGeneratorFunc = function*() {
99
>f : FuncOrGeneratorFunc
10-
>function*() { yield (num) => console.log(num); // `num` should be inferred to have type `number`.} : () => Generator<(num: number) => void, void, unknown>
10+
>function*() { yield (num) => console.log(num); // `num` should be inferred to have type `number`.} : () => Generator<(num: number) => void, void, void>
1111

1212
yield (num) => console.log(num); // `num` should be inferred to have type `number`.
13-
>yield (num) => console.log(num) : any
13+
>yield (num) => console.log(num) : void
1414
>(num) => console.log(num) : (num: number) => void
1515
>num : number
1616
>console.log(num) : void

tests/baselines/reference/contextualTypeOnYield2.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ type OrGen = () => (number | Generator<string, (arg: number) => void, undefined>
77

88
const g: OrGen = function* () {
99
>g : OrGen
10-
>function* () { return (num) => console.log(num);} : () => Generator<never, (num: number) => void, unknown>
10+
>function* () { return (num) => console.log(num);} : () => Generator<never, (num: number) => void, undefined>
1111

1212
return (num) => console.log(num);
1313
>(num) => console.log(num) : (num: number) => void

tests/baselines/reference/contextuallyTypeAsyncFunctionReturnType.symbols

+179
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,182 @@ async function fn4(): Promise<Obj> {
5454

5555
});
5656
}
57+
58+
declare class Context {
59+
>Context : Symbol(Context, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 20, 1))
60+
61+
private _runnable;
62+
>_runnable : Symbol(Context._runnable, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 22, 23))
63+
}
64+
type Done = (err?: any) => void;
65+
>Done : Symbol(Done, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 24, 1))
66+
>err : Symbol(err, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 25, 13))
67+
68+
type Func = (this: Context, done: Done) => void;
69+
>Func : Symbol(Func, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 25, 32))
70+
>this : Symbol(this, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 26, 13))
71+
>Context : Symbol(Context, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 20, 1))
72+
>done : Symbol(done, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 26, 27))
73+
>Done : Symbol(Done, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 24, 1))
74+
75+
type AsyncFunc = (this: Context) => PromiseLike<any>;
76+
>AsyncFunc : Symbol(AsyncFunc, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 26, 48))
77+
>this : Symbol(this, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 27, 18))
78+
>Context : Symbol(Context, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 20, 1))
79+
>PromiseLike : Symbol(PromiseLike, Decl(lib.es5.d.ts, --, --))
80+
81+
interface TestFunction {
82+
>TestFunction : Symbol(TestFunction, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 27, 53))
83+
84+
(fn: Func): void;
85+
>fn : Symbol(fn, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 30, 3))
86+
>Func : Symbol(Func, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 25, 32))
87+
88+
(fn: AsyncFunc): void;
89+
>fn : Symbol(fn, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 31, 3))
90+
>AsyncFunc : Symbol(AsyncFunc, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 26, 48))
91+
92+
(title: string, fn?: Func): void;
93+
>title : Symbol(title, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 32, 3))
94+
>fn : Symbol(fn, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 32, 17))
95+
>Func : Symbol(Func, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 25, 32))
96+
97+
(title: string, fn?: AsyncFunc): void;
98+
>title : Symbol(title, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 33, 3))
99+
>fn : Symbol(fn, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 33, 17))
100+
>AsyncFunc : Symbol(AsyncFunc, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 26, 48))
101+
}
102+
103+
declare const test: TestFunction;
104+
>test : Symbol(test, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 36, 13))
105+
>TestFunction : Symbol(TestFunction, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 27, 53))
106+
107+
interface ProcessTreeNode {
108+
>ProcessTreeNode : Symbol(ProcessTreeNode, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 36, 33))
109+
110+
pid: number;
111+
>pid : Symbol(ProcessTreeNode.pid, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 38, 27))
112+
113+
name: string;
114+
>name : Symbol(ProcessTreeNode.name, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 39, 14))
115+
116+
memory?: number;
117+
>memory : Symbol(ProcessTreeNode.memory, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 40, 15))
118+
119+
commandLine?: string;
120+
>commandLine : Symbol(ProcessTreeNode.commandLine, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 41, 18))
121+
122+
children: ProcessTreeNode[];
123+
>children : Symbol(ProcessTreeNode.children, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 42, 23))
124+
>ProcessTreeNode : Symbol(ProcessTreeNode, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 36, 33))
125+
}
126+
127+
export declare function getProcessTree(
128+
>getProcessTree : Symbol(getProcessTree, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 44, 1))
129+
130+
rootPid: number,
131+
>rootPid : Symbol(rootPid, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 46, 39))
132+
133+
callback: (tree: ProcessTreeNode) => void
134+
>callback : Symbol(callback, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 47, 18))
135+
>tree : Symbol(tree, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 48, 13))
136+
>ProcessTreeNode : Symbol(ProcessTreeNode, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 36, 33))
137+
138+
): void;
139+
140+
test("windows-process-tree", async () => {
141+
>test : Symbol(test, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 36, 13))
142+
143+
return new Promise((resolve, reject) => {
144+
>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, --, --))
145+
>resolve : Symbol(resolve, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 52, 22))
146+
>reject : Symbol(reject, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 52, 30))
147+
148+
getProcessTree(123, (tree) => {
149+
>getProcessTree : Symbol(getProcessTree, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 44, 1))
150+
>tree : Symbol(tree, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 53, 25))
151+
152+
if (tree) {
153+
>tree : Symbol(tree, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 53, 25))
154+
155+
resolve();
156+
>resolve : Symbol(resolve, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 52, 22))
157+
158+
} else {
159+
reject(new Error("windows-process-tree"));
160+
>reject : Symbol(reject, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 52, 30))
161+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --))
162+
}
163+
});
164+
});
165+
});
166+
167+
interface ILocalExtension {
168+
>ILocalExtension : Symbol(ILocalExtension, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 61, 3))
169+
170+
isApplicationScoped: boolean;
171+
>isApplicationScoped : Symbol(ILocalExtension.isApplicationScoped, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 63, 27))
172+
173+
publisherId: string | null;
174+
>publisherId : Symbol(ILocalExtension.publisherId, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 64, 31))
175+
}
176+
type Metadata = {
177+
>Metadata : Symbol(Metadata, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 66, 1))
178+
179+
updated: boolean;
180+
>updated : Symbol(updated, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 67, 17))
181+
182+
};
183+
declare function scanMetadata(
184+
>scanMetadata : Symbol(scanMetadata, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 69, 2))
185+
186+
local: ILocalExtension
187+
>local : Symbol(local, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 70, 30))
188+
>ILocalExtension : Symbol(ILocalExtension, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 61, 3))
189+
190+
): Promise<Metadata | undefined>;
191+
>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, --, --))
192+
>Metadata : Symbol(Metadata, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 66, 1))
193+
194+
async function copyExtensions(
195+
>copyExtensions : Symbol(copyExtensions, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 72, 33))
196+
197+
fromExtensions: ILocalExtension[]
198+
>fromExtensions : Symbol(fromExtensions, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 74, 30))
199+
>ILocalExtension : Symbol(ILocalExtension, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 61, 3))
200+
201+
): Promise<void> {
202+
>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, --, --))
203+
204+
const extensions: [ILocalExtension, Metadata | undefined][] =
205+
>extensions : Symbol(extensions, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 77, 7))
206+
>ILocalExtension : Symbol(ILocalExtension, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 61, 3))
207+
>Metadata : Symbol(Metadata, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 66, 1))
208+
209+
await Promise.all(
210+
>Promise.all : Symbol(PromiseConstructor.all, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
211+
>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, --, --))
212+
>all : Symbol(PromiseConstructor.all, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
213+
214+
fromExtensions
215+
>fromExtensions .filter((e) => !e.isApplicationScoped) .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
216+
>fromExtensions .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
217+
>fromExtensions : Symbol(fromExtensions, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 74, 30))
218+
219+
.filter((e) => !e.isApplicationScoped)
220+
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
221+
>e : Symbol(e, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 80, 17))
222+
>e.isApplicationScoped : Symbol(ILocalExtension.isApplicationScoped, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 63, 27))
223+
>e : Symbol(e, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 80, 17))
224+
>isApplicationScoped : Symbol(ILocalExtension.isApplicationScoped, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 63, 27))
225+
226+
.map(async (e) => [e, await scanMetadata(e)])
227+
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
228+
>e : Symbol(e, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 81, 20))
229+
>e : Symbol(e, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 81, 20))
230+
>scanMetadata : Symbol(scanMetadata, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 69, 2))
231+
>e : Symbol(e, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 81, 20))
232+
233+
);
234+
}
235+

0 commit comments

Comments
 (0)