From 00b1a77450f5be475ba129d90ce0e28307a091ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 31 Jan 2022 20:09:22 +0100 Subject: [PATCH 1/4] Add a test case for contextually typed return types of an async function when the contextual type is a union of a promise and of a different type --- ...syncFunctionReturnTypeFromUnion.errors.txt | 53 +++++++++++++++ ...peAsyncFunctionReturnTypeFromUnion.symbols | 64 ++++++++++++++++++ ...TypeAsyncFunctionReturnTypeFromUnion.types | 65 +++++++++++++++++++ ...llyTypeAsyncFunctionReturnTypeFromUnion.ts | 24 +++++++ 4 files changed, 206 insertions(+) create mode 100644 tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.errors.txt create mode 100644 tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.symbols create mode 100644 tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.types create mode 100644 tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts diff --git a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.errors.txt b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.errors.txt new file mode 100644 index 0000000000000..dd35047d9b936 --- /dev/null +++ b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.errors.txt @@ -0,0 +1,53 @@ +tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts(11,23): error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }> | StateMachine<{ count: number; }>'. + Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }>'. + Type '{ count: number; } | StateMachine<{ count: number; }>' is not assignable to type '{ count: number; }'. + Property 'count' is missing in type 'StateMachine<{ count: number; }>' but required in type '{ count: number; }'. +tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts(12,11): error TS2322: Type '() => Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type '() => Promise<{ count: number; }> | StateMachine<{ count: number; }>'. + Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }> | StateMachine<{ count: number; }>'. + Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }>'. +tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts(19,22): error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }> | StateMachine<{ count: number; }>'. + Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }>'. + Type '{ count: number; } | StateMachine<{ count: number; }>' is not assignable to type '{ count: number; }'. + Property 'count' is missing in type 'StateMachine<{ count: number; }>' but required in type '{ count: number; }'. + + +==== tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts (3 errors) ==== + 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"), + ~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }> | StateMachine<{ count: number; }>'. +!!! error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }>'. +!!! error TS2322: Type '{ count: number; } | StateMachine<{ count: number; }>' is not assignable to type '{ count: number; }'. +!!! error TS2322: Property 'count' is missing in type 'StateMachine<{ count: number; }>' but required in type '{ count: number; }'. +!!! related TS2728 tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts:9:17: 'count' is declared here. +!!! related TS6502 tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts:6:28: The expected type comes from the return type of this signature. + async test2() { + ~~~~~ +!!! error TS2322: Type '() => Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type '() => Promise<{ count: number; }> | StateMachine<{ count: number; }>'. +!!! error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }> | StateMachine<{ count: number; }>'. +!!! error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }>'. + return Promise.reject("some err"); + }, + }, + }); + + function fn1(): () => Promise<{ count: number }> | StateMachine<{ count: number }> { + return async () => Promise.reject('some err') + ~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }> | StateMachine<{ count: number; }>'. +!!! error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }>'. +!!! error TS2322: Type '{ count: number; } | StateMachine<{ count: number; }>' is not assignable to type '{ count: number; }'. +!!! error TS2322: Property 'count' is missing in type 'StateMachine<{ count: number; }>' but required in type '{ count: number; }'. +!!! related TS2728 tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts:18:33: 'count' is declared here. +!!! related TS6502 tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts:18:17: The expected type comes from the return type of this signature. + } + \ No newline at end of file diff --git a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.symbols b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.symbols new file mode 100644 index 0000000000000..02c4a20dacc84 --- /dev/null +++ b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.symbols @@ -0,0 +1,64 @@ +=== tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts === +declare class StateMachine { +>StateMachine : Symbol(StateMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 0, 0)) +>T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 0, 27)) + + onDone: (a: T) => void; +>onDone : Symbol(StateMachine.onDone, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 0, 31)) +>a : Symbol(a, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 1, 11)) +>T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 0, 27)) +} + +declare function createMachine(implementations: { +>createMachine : Symbol(createMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 2, 1)) +>T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 4, 31)) +>implementations : Symbol(implementations, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 4, 34)) + + services: Record Promise | StateMachine>; +>services : Symbol(services, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 4, 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, 4, 31)) +>StateMachine : Symbol(StateMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 0, 0)) +>T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 4, 31)) + +}): void; + +createMachine<{ count: number }>({ +>createMachine : Symbol(createMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 2, 1)) +>count : Symbol(count, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 8, 15)) + + services: { +>services : Symbol(services, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 8, 34)) + + test: async () => Promise.reject("some err"), +>test : Symbol(test, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 9, 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, 10, 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, 15, 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, 17, 31)) +>StateMachine : Symbol(StateMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 0, 0)) +>count : Symbol(count, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 17, 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, --, --)) +} + diff --git a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.types b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.types new file mode 100644 index 0000000000000..fbd262ef47ead --- /dev/null +++ b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.types @@ -0,0 +1,65 @@ +=== tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts === +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; } | StateMachine<{ count: number; }>>; test2(): Promise<{ count: number; } | StateMachine<{ count: number; }>>; }; } + + services: { +>services : { test: () => Promise<{ count: number; } | StateMachine<{ count: number; }>>; test2(): Promise<{ count: number; } | StateMachine<{ count: number; }>>; } +>{ test: async () => Promise.reject("some err"), async test2() { return Promise.reject("some err"); }, } : { test: () => Promise<{ count: number; } | StateMachine<{ count: number; }>>; test2(): Promise<{ count: number; } | StateMachine<{ count: number; }>>; } + + test: async () => Promise.reject("some err"), +>test : () => Promise<{ count: number; } | StateMachine<{ count: number; }>> +>async () => Promise.reject("some err") : () => Promise<{ count: number; } | StateMachine<{ count: number; }>> +>Promise.reject("some err") : Promise<{ count: number; } | StateMachine<{ count: number; }>> +>Promise.reject : (reason?: any) => Promise +>Promise : PromiseConstructor +>reject : (reason?: any) => Promise +>"some err" : "some err" + + async test2() { +>test2 : () => Promise<{ count: number; } | StateMachine<{ count: number; }>> + + return Promise.reject("some err"); +>Promise.reject("some err") : Promise<{ count: number; } | StateMachine<{ 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; } | StateMachine<{ count: number; }>> +>Promise.reject('some err') : Promise<{ count: number; } | StateMachine<{ count: number; }>> +>Promise.reject : (reason?: any) => Promise +>Promise : PromiseConstructor +>reject : (reason?: any) => Promise +>'some err' : "some err" +} + 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..3de2b700e2883 --- /dev/null +++ b/tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts @@ -0,0 +1,24 @@ +// @target: esnext +// @strict: true +// @noEmit: true + +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') +} From dd38c4dd369e6812c725c1d82c367da86ff6268d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 31 Jan 2022 20:13:09 +0100 Subject: [PATCH 2/4] Fixed the issue with contextually typed return type of an async function when unions are involved --- src/compiler/checker.ts | 4 +- ...syncFunctionReturnTypeFromUnion.errors.txt | 53 ------------------- ...TypeAsyncFunctionReturnTypeFromUnion.types | 20 +++---- 3 files changed, 11 insertions(+), 66 deletions(-) delete mode 100644 tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 11ac767e5aeb3..073aa62e7c952 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26691,12 +26691,10 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") return undefined; } contextualReturnType = iterationTypes.returnType; - // falls through to unwrap Promise for AsyncGenerators } if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function - // Get the awaited type without the `Awaited` alias - const contextualAwaitedType = mapType(contextualReturnType, getAwaitedTypeNoAlias); + const contextualAwaitedType = mapType(contextualReturnType, functionFlags & FunctionFlags.Generator ? getAwaitedTypeNoAlias : getAwaitedTypeOfPromise); return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } diff --git a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.errors.txt b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.errors.txt deleted file mode 100644 index dd35047d9b936..0000000000000 --- a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.errors.txt +++ /dev/null @@ -1,53 +0,0 @@ -tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts(11,23): error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }> | StateMachine<{ count: number; }>'. - Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }>'. - Type '{ count: number; } | StateMachine<{ count: number; }>' is not assignable to type '{ count: number; }'. - Property 'count' is missing in type 'StateMachine<{ count: number; }>' but required in type '{ count: number; }'. -tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts(12,11): error TS2322: Type '() => Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type '() => Promise<{ count: number; }> | StateMachine<{ count: number; }>'. - Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }> | StateMachine<{ count: number; }>'. - Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }>'. -tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts(19,22): error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }> | StateMachine<{ count: number; }>'. - Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }>'. - Type '{ count: number; } | StateMachine<{ count: number; }>' is not assignable to type '{ count: number; }'. - Property 'count' is missing in type 'StateMachine<{ count: number; }>' but required in type '{ count: number; }'. - - -==== tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts (3 errors) ==== - 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"), - ~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }> | StateMachine<{ count: number; }>'. -!!! error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }>'. -!!! error TS2322: Type '{ count: number; } | StateMachine<{ count: number; }>' is not assignable to type '{ count: number; }'. -!!! error TS2322: Property 'count' is missing in type 'StateMachine<{ count: number; }>' but required in type '{ count: number; }'. -!!! related TS2728 tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts:9:17: 'count' is declared here. -!!! related TS6502 tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts:6:28: The expected type comes from the return type of this signature. - async test2() { - ~~~~~ -!!! error TS2322: Type '() => Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type '() => Promise<{ count: number; }> | StateMachine<{ count: number; }>'. -!!! error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }> | StateMachine<{ count: number; }>'. -!!! error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }>'. - return Promise.reject("some err"); - }, - }, - }); - - function fn1(): () => Promise<{ count: number }> | StateMachine<{ count: number }> { - return async () => Promise.reject('some err') - ~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }> | StateMachine<{ count: number; }>'. -!!! error TS2322: Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }>'. -!!! error TS2322: Type '{ count: number; } | StateMachine<{ count: number; }>' is not assignable to type '{ count: number; }'. -!!! error TS2322: Property 'count' is missing in type 'StateMachine<{ count: number; }>' but required in type '{ count: number; }'. -!!! related TS2728 tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts:18:33: 'count' is declared here. -!!! related TS6502 tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts:18:17: The expected type comes from the return type of this signature. - } - \ No newline at end of file diff --git a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.types b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.types index fbd262ef47ead..1a025061ecc97 100644 --- a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.types +++ b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.types @@ -20,26 +20,26 @@ 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; } | StateMachine<{ count: number; }>>; test2(): Promise<{ count: number; } | StateMachine<{ 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; } | StateMachine<{ count: number; }>>; test2(): Promise<{ count: number; } | StateMachine<{ count: number; }>>; } ->{ test: async () => Promise.reject("some err"), async test2() { return Promise.reject("some err"); }, } : { test: () => Promise<{ count: number; } | StateMachine<{ count: number; }>>; test2(): Promise<{ count: number; } | StateMachine<{ count: number; }>>; } +>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; } | StateMachine<{ count: number; }>> ->async () => Promise.reject("some err") : () => Promise<{ count: number; } | StateMachine<{ count: number; }>> ->Promise.reject("some err") : Promise<{ count: number; } | StateMachine<{ count: number; }>> +>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; } | StateMachine<{ count: number; }>> +>test2 : () => Promise<{ count: number; }> return Promise.reject("some err"); ->Promise.reject("some err") : Promise<{ count: number; } | StateMachine<{ count: number; }>> +>Promise.reject("some err") : Promise<{ count: number; }> >Promise.reject : (reason?: any) => Promise >Promise : PromiseConstructor >reject : (reason?: any) => Promise @@ -55,8 +55,8 @@ function fn1(): () => Promise<{ count: number }> | StateMachine<{ count: number >count : number return async () => Promise.reject('some err') ->async () => Promise.reject('some err') : () => Promise<{ count: number; } | StateMachine<{ count: number; }>> ->Promise.reject('some err') : Promise<{ count: number; } | StateMachine<{ 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 From f4b2ec7625392212a353272636e11d53becc4a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 31 Jan 2022 20:19:41 +0100 Subject: [PATCH 3/4] Refactor conditional logic to avoid fallthrough and to make the control flow more clear --- src/compiler/checker.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 073aa62e7c952..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,12 +26690,17 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") if (!iterationTypes) { return undefined; } - contextualReturnType = iterationTypes.returnType; + 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 - const contextualAwaitedType = mapType(contextualReturnType, functionFlags & FunctionFlags.Generator ? getAwaitedTypeNoAlias : getAwaitedTypeOfPromise); - 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 From ce8c01201397be12642a7f1deabe320cf2afc5f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 5 Jul 2022 10:22:14 +0200 Subject: [PATCH 4/4] Add additional test case from the issue comment --- ...peAsyncFunctionReturnTypeFromUnion.symbols | 72 ++++++++++++++----- ...TypeAsyncFunctionReturnTypeFromUnion.types | 41 +++++++++++ ...llyTypeAsyncFunctionReturnTypeFromUnion.ts | 13 ++++ 3 files changed, 108 insertions(+), 18 deletions(-) diff --git a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.symbols b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.symbols index 02c4a20dacc84..7b5af1e95d49e 100644 --- a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.symbols +++ b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.symbols @@ -1,44 +1,46 @@ === 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, 0, 27)) +>T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 2, 27)) onDone: (a: T) => void; ->onDone : Symbol(StateMachine.onDone, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 0, 31)) ->a : Symbol(a, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 1, 11)) ->T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 0, 27)) +>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, 2, 1)) ->T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 4, 31)) ->implementations : Symbol(implementations, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 4, 34)) +>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, 4, 52)) +>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, 4, 31)) +>T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 6, 31)) >StateMachine : Symbol(StateMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 0, 0)) ->T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 4, 31)) +>T : Symbol(T, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 6, 31)) }): void; createMachine<{ count: number }>({ ->createMachine : Symbol(createMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 2, 1)) ->count : Symbol(count, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 8, 15)) +>createMachine : Symbol(createMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 4, 1)) +>count : Symbol(count, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 10, 15)) services: { ->services : Symbol(services, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 8, 34)) +>services : Symbol(services, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 10, 34)) test: async () => Promise.reject("some err"), ->test : Symbol(test, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 9, 13)) +>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, 10, 49)) +>test2 : Symbol(test2, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 12, 49)) return Promise.reject("some err"); >Promise.reject : Symbol(PromiseConstructor.reject, Decl(lib.es2015.promise.d.ts, --, --)) @@ -50,11 +52,11 @@ createMachine<{ count: number }>({ }); function fn1(): () => Promise<{ count: number }> | StateMachine<{ count: number }> { ->fn1 : Symbol(fn1, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 15, 3)) +>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, 17, 31)) +>count : Symbol(count, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 19, 31)) >StateMachine : Symbol(StateMachine, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 0, 0)) ->count : Symbol(count, Decl(contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts, 17, 65)) +>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, --, --)) @@ -62,3 +64,37 @@ function fn1(): () => Promise<{ count: number }> | StateMachine<{ count: number >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 index 1a025061ecc97..c814793c1ca2a 100644 --- a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.types +++ b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnTypeFromUnion.types @@ -1,4 +1,6 @@ === tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts === +// repro #47682 + declare class StateMachine { >StateMachine : StateMachine @@ -63,3 +65,42 @@ function fn1(): () => Promise<{ count: number }> | StateMachine<{ count: number >'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 index 3de2b700e2883..ede956afadc1d 100644 --- a/tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts +++ b/tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnTypeFromUnion.ts @@ -2,6 +2,8 @@ // @strict: true // @noEmit: true +// repro #47682 + declare class StateMachine { onDone: (a: T) => void; } @@ -22,3 +24,14 @@ createMachine<{ count: number }>({ 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);