diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 11ac767e5aeb3..cb02d161cf850 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26681,7 +26681,7 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") function getContextualTypeForReturnExpression(node: Expression): Type | undefined { const func = getContainingFunction(node); if (func) { - let contextualReturnType = getContextualReturnType(func); + const contextualReturnType = getContextualReturnType(func); if (contextualReturnType) { const functionFlags = getFunctionFlags(func); if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function @@ -26690,14 +26690,17 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") if (!iterationTypes) { return undefined; } - contextualReturnType = iterationTypes.returnType; - // falls through to unwrap Promise for AsyncGenerators + if (functionFlags & FunctionFlags.Async) { + // unwrap Promise to get the awaited type without the `Awaited` alias + const contextualAwaitedType = mapType(iterationTypes.returnType, getAwaitedTypeNoAlias); + return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); + } + return iterationTypes.returnType; } - if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function - // Get the awaited type without the `Awaited` alias - const contextualAwaitedType = mapType(contextualReturnType, getAwaitedTypeNoAlias); - return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); + if (functionFlags & FunctionFlags.Async) { + const contextualTypeOfPromise = mapType(contextualReturnType, getAwaitedTypeOfPromise); + return contextualTypeOfPromise && getUnionType([contextualTypeOfPromise, createPromiseLikeType(contextualTypeOfPromise)]); } return contextualReturnType; // Regular function or Generator function diff --git a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.symbols b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.symbols new file mode 100644 index 0000000000000..7b5af1e95d49e --- /dev/null +++ b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.symbols @@ -0,0 +1,100 @@ +=== tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts === +// repro #47682 + +declare class StateMachine { +>StateMachine : Symbol(StateMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 0, 0)) +>T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 2, 27)) + + onDone: (a: T) => void; +>onDone : Symbol(StateMachine.onDone, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 2, 31)) +>a : Symbol(a, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 3, 11)) +>T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 2, 27)) +} + +declare function createMachine(implementations: { +>createMachine : Symbol(createMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 4, 1)) +>T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 6, 31)) +>implementations : Symbol(implementations, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 6, 34)) + + services: Record Promise | StateMachine>; +>services : Symbol(services, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 6, 52)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>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, --, --)) +>T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 6, 31)) +>StateMachine : Symbol(StateMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 0, 0)) +>T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 6, 31)) + +}): void; + +createMachine<{ count: number }>({ +>createMachine : Symbol(createMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 4, 1)) +>count : Symbol(count, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 10, 15)) + + services: { +>services : Symbol(services, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 10, 34)) + + test: async () => Promise.reject("some err"), +>test : Symbol(test, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 11, 13)) +>Promise.reject : Symbol(PromiseConstructor.reject, Decl(lib.es2015.promise.d.ts, --, --)) +>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, --, --)) +>reject : Symbol(PromiseConstructor.reject, Decl(lib.es2015.promise.d.ts, --, --)) + + async test2() { +>test2 : Symbol(test2, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 12, 49)) + + return Promise.reject("some err"); +>Promise.reject : Symbol(PromiseConstructor.reject, Decl(lib.es2015.promise.d.ts, --, --)) +>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, --, --)) +>reject : Symbol(PromiseConstructor.reject, Decl(lib.es2015.promise.d.ts, --, --)) + + }, + }, +}); + +function fn1(): () => Promise<{ count: number }> | StateMachine<{ count: number }> { +>fn1 : Symbol(fn1, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 17, 3)) +>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, --, --)) +>count : Symbol(count, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 19, 31)) +>StateMachine : Symbol(StateMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 0, 0)) +>count : Symbol(count, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 19, 65)) + + return async () => Promise.reject('some err') +>Promise.reject : Symbol(PromiseConstructor.reject, Decl(lib.es2015.promise.d.ts, --, --)) +>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, --, --)) +>reject : Symbol(PromiseConstructor.reject, Decl(lib.es2015.promise.d.ts, --, --)) +} + +// repro #47682 issuecomment-1174099713 + +declare function load(): Promise; +>load : Symbol(load, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 21, 1)) +>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, --, --)) + +type LoadCallback = () => Promise | string; +>LoadCallback : Symbol(LoadCallback, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 25, 42)) +>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, --, --)) + +// all of those are essentially the same and should be allowed +const cb1: LoadCallback = async () => load().then(m => m); +>cb1 : Symbol(cb1, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 30, 5)) +>LoadCallback : Symbol(LoadCallback, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 25, 42)) +>load().then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>load : Symbol(load, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 21, 1)) +>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>m : Symbol(m, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 30, 50)) +>m : Symbol(m, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 30, 50)) + +const cb2: LoadCallback = async () => load(); +>cb2 : Symbol(cb2, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 31, 5)) +>LoadCallback : Symbol(LoadCallback, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 25, 42)) +>load : Symbol(load, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 21, 1)) + +const cb3: LoadCallback = () => load().then(m => m); +>cb3 : Symbol(cb3, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 32, 5)) +>LoadCallback : Symbol(LoadCallback, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 25, 42)) +>load().then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>load : Symbol(load, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 21, 1)) +>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>m : Symbol(m, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 32, 44)) +>m : Symbol(m, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 32, 44)) + diff --git a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.types b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.types new file mode 100644 index 0000000000000..c814793c1ca2a --- /dev/null +++ b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.types @@ -0,0 +1,106 @@ +=== tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts === +// repro #47682 + +declare class StateMachine { +>StateMachine : StateMachine + + onDone: (a: T) => void; +>onDone : (a: T) => void +>a : T +} + +declare function createMachine(implementations: { +>createMachine : (implementations: { services: Record Promise | StateMachine>;}) => void +>implementations : { services: Record Promise | StateMachine>; } + + services: Record Promise | StateMachine>; +>services : Record Promise | StateMachine> + +}): void; + +createMachine<{ count: number }>({ +>createMachine<{ count: number }>({ services: { test: async () => Promise.reject("some err"), async test2() { return Promise.reject("some err"); }, },}) : void +>createMachine : (implementations: { services: Record Promise | StateMachine>; }) => void +>count : number +>{ services: { test: async () => Promise.reject("some err"), async test2() { return Promise.reject("some err"); }, },} : { services: { test: () => Promise<{ count: number; }>; test2(): Promise<{ count: number; }>; }; } + + services: { +>services : { test: () => Promise<{ count: number; }>; test2(): Promise<{ count: number; }>; } +>{ test: async () => Promise.reject("some err"), async test2() { return Promise.reject("some err"); }, } : { test: () => Promise<{ count: number; }>; test2(): Promise<{ count: number; }>; } + + test: async () => Promise.reject("some err"), +>test : () => Promise<{ count: number; }> +>async () => Promise.reject("some err") : () => Promise<{ count: number; }> +>Promise.reject("some err") : Promise<{ count: number; }> +>Promise.reject : (reason?: any) => Promise +>Promise : PromiseConstructor +>reject : (reason?: any) => Promise +>"some err" : "some err" + + async test2() { +>test2 : () => Promise<{ count: number; }> + + return Promise.reject("some err"); +>Promise.reject("some err") : Promise<{ count: number; }> +>Promise.reject : (reason?: any) => Promise +>Promise : PromiseConstructor +>reject : (reason?: any) => Promise +>"some err" : "some err" + + }, + }, +}); + +function fn1(): () => Promise<{ count: number }> | StateMachine<{ count: number }> { +>fn1 : () => () => Promise<{ count: number;}> | StateMachine<{ count: number;}> +>count : number +>count : number + + return async () => Promise.reject('some err') +>async () => Promise.reject('some err') : () => Promise<{ count: number; }> +>Promise.reject('some err') : Promise<{ count: number; }> +>Promise.reject : (reason?: any) => Promise +>Promise : PromiseConstructor +>reject : (reason?: any) => Promise +>'some err' : "some err" +} + +// repro #47682 issuecomment-1174099713 + +declare function load(): Promise; +>load : () => Promise + +type LoadCallback = () => Promise | string; +>LoadCallback : () => Promise | string + +// all of those are essentially the same and should be allowed +const cb1: LoadCallback = async () => load().then(m => m); +>cb1 : LoadCallback +>async () => load().then(m => m) : () => Promise +>load().then(m => m) : Promise +>load().then : (onfulfilled?: ((value: boolean) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined) => Promise +>load() : Promise +>load : () => Promise +>then : (onfulfilled?: ((value: boolean) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined) => Promise +>m => m : (m: boolean) => boolean +>m : boolean +>m : boolean + +const cb2: LoadCallback = async () => load(); +>cb2 : LoadCallback +>async () => load() : () => Promise +>load() : Promise +>load : () => Promise + +const cb3: LoadCallback = () => load().then(m => m); +>cb3 : LoadCallback +>() => load().then(m => m) : () => Promise +>load().then(m => m) : Promise +>load().then : (onfulfilled?: ((value: boolean) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined) => Promise +>load() : Promise +>load : () => Promise +>then : (onfulfilled?: ((value: boolean) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined) => Promise +>m => m : (m: boolean) => boolean +>m : boolean +>m : boolean + diff --git a/tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts b/tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts new file mode 100644 index 0000000000000..ede956afadc1d --- /dev/null +++ b/tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts @@ -0,0 +1,37 @@ +// @target: esnext +// @strict: true +// @noEmit: true + +// repro #47682 + +declare class StateMachine { + onDone: (a: T) => void; +} + +declare function createMachine(implementations: { + services: Record Promise | StateMachine>; +}): void; + +createMachine<{ count: number }>({ + services: { + test: async () => Promise.reject("some err"), + async test2() { + return Promise.reject("some err"); + }, + }, +}); + +function fn1(): () => Promise<{ count: number }> | StateMachine<{ count: number }> { + return async () => Promise.reject('some err') +} + +// repro #47682 issuecomment-1174099713 + +declare function load(): Promise; + +type LoadCallback = () => Promise | string; + +// all of those are essentially the same and should be allowed +const cb1: LoadCallback = async () => load().then(m => m); +const cb2: LoadCallback = async () => load(); +const cb3: LoadCallback = () => load().then(m => m);