diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 2a4ead5003a89..c330f317791b5 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -29795,7 +29795,8 @@ func (c *Checker) getContextualReturnType(functionDecl *ast.Node, contextFlags C // Otherwise, if the containing function is contextually typed by a function type with exactly one call signature // and that call signature is non-generic, return statements are contextually typed by the return type of the signature signature := c.getContextualSignatureForFunctionLikeDeclaration(functionDecl) - if signature != nil && !c.isResolvingReturnTypeOfSignature(signature) { + // A function's own inferred return type must not become contextual evidence for its body. + if signature != nil && signature != c.getSignatureFromDeclaration(functionDecl) && !c.isResolvingReturnTypeOfSignature(signature) { returnType := c.getReturnTypeOfSignature(signature) functionFlags := ast.GetFunctionFlags(functionDecl) if functionFlags&ast.FunctionFlagsGenerator != 0 { diff --git a/tsc/internal/fourslash/tests/quickInfoGenericDefaultInReentrantCallResolution_test.go b/tsc/internal/fourslash/tests/quickInfoGenericDefaultInReentrantCallResolution_test.go new file mode 100644 index 0000000000000..643fa015bf077 --- /dev/null +++ b/tsc/internal/fourslash/tests/quickInfoGenericDefaultInReentrantCallResolution_test.go @@ -0,0 +1,48 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/fourslash" + "github.com/microsoft/TypeScript/tsc/internal/testutil" +) + +func TestQuickInfoGenericDefaultInReentrantCallResolution(t *testing.T) { + t.Parallel() + const content = `// @target: es2015 +// @strict: true +declare function fetcher(cfg: unknown): Promise; +type Routes = R extends "x" ? { IN: { v: number }; OUT: string } : never; +function executeFetch>( + route: R, + body?: T extends Record<"IN", any> ? T["IN"] : never, +) { + return fetcher ? T["OUT"] : T>({ route, body }); +} +declare function useCallback(cb: T, deps: unknown[]): T; +export const /*callback*/cb = useCallback((data: { v: number }) => { + return executeFetch("x", /*argument*/data); +}, []); +export const check: Promise = cb({ v: 1 });` + for _, first := range []string{"callback", "argument", "diagnostics"} { + t.Run(first, func(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + callbackInfo := "const cb: (data: {\n v: number;\n}) => Promise" + argumentInfo := "(parameter) data: {\n v: number;\n}" + switch first { + case "callback": + f.VerifyQuickInfoAt(t, "callback", callbackInfo, "") + case "argument": + f.VerifyQuickInfoAt(t, "argument", argumentInfo, "") + case "diagnostics": + f.VerifyNoErrors(t) + } + f.VerifyQuickInfoAt(t, "callback", callbackInfo, "") + f.VerifyQuickInfoAt(t, "argument", argumentInfo, "") + f.VerifyNoErrors(t) + }) + } +} diff --git a/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.symbols b/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.symbols new file mode 100644 index 0000000000000..29042053067da --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.symbols @@ -0,0 +1,114 @@ +//// [tests/cases/compiler/genericDefaultInReentrantCallResolution.ts] //// + +=== genericDefaultInReentrantCallResolution.ts === +// https://github.com/microsoft/TypeScript/issues/63949 + +declare function fetcher(cfg: unknown): Promise; +>fetcher : Symbol(fetcher, Decl(genericDefaultInReentrantCallResolution.ts, 0, 0)) +>D : Symbol(D, Decl(genericDefaultInReentrantCallResolution.ts, 2, 25)) +>cfg : Symbol(cfg, Decl(genericDefaultInReentrantCallResolution.ts, 2, 28)) +>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, --, --)) +>D : Symbol(D, Decl(genericDefaultInReentrantCallResolution.ts, 2, 25)) + +type Routes = R extends "x" ? { IN: { v: number }; OUT: string } : never; +>Routes : Symbol(Routes, Decl(genericDefaultInReentrantCallResolution.ts, 2, 54)) +>R : Symbol(R, Decl(genericDefaultInReentrantCallResolution.ts, 4, 12)) +>R : Symbol(R, Decl(genericDefaultInReentrantCallResolution.ts, 4, 12)) +>IN : Symbol(IN, Decl(genericDefaultInReentrantCallResolution.ts, 4, 34)) +>v : Symbol(v, Decl(genericDefaultInReentrantCallResolution.ts, 4, 40)) +>OUT : Symbol(OUT, Decl(genericDefaultInReentrantCallResolution.ts, 4, 53)) + +function executeFetch>( +>executeFetch : Symbol(executeFetch, Decl(genericDefaultInReentrantCallResolution.ts, 4, 76)) +>R : Symbol(R, Decl(genericDefaultInReentrantCallResolution.ts, 6, 22)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 6, 39)) +>Routes : Symbol(Routes, Decl(genericDefaultInReentrantCallResolution.ts, 2, 54)) +>R : Symbol(R, Decl(genericDefaultInReentrantCallResolution.ts, 6, 22)) + + route: R, +>route : Symbol(route, Decl(genericDefaultInReentrantCallResolution.ts, 6, 55)) +>R : Symbol(R, Decl(genericDefaultInReentrantCallResolution.ts, 6, 22)) + + body?: T extends Record<"IN", any> ? T["IN"] : never, +>body : Symbol(body, Decl(genericDefaultInReentrantCallResolution.ts, 7, 13)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 6, 39)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 6, 39)) + +) { + return fetcher ? T["OUT"] : T>({ route, body }); +>fetcher : Symbol(fetcher, Decl(genericDefaultInReentrantCallResolution.ts, 0, 0)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 6, 39)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 6, 39)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 6, 39)) +>route : Symbol(route, Decl(genericDefaultInReentrantCallResolution.ts, 10, 65)) +>body : Symbol(body, Decl(genericDefaultInReentrantCallResolution.ts, 10, 72)) +} + +declare function useCallback(cb: T, deps: unknown[]): T; +>useCallback : Symbol(useCallback, Decl(genericDefaultInReentrantCallResolution.ts, 11, 1)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 13, 29)) +>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>cb : Symbol(cb, Decl(genericDefaultInReentrantCallResolution.ts, 13, 49)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 13, 29)) +>deps : Symbol(deps, Decl(genericDefaultInReentrantCallResolution.ts, 13, 55)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 13, 29)) + +export const cb = useCallback((data: { v: number }) => { +>cb : Symbol(cb, Decl(genericDefaultInReentrantCallResolution.ts, 15, 12)) +>useCallback : Symbol(useCallback, Decl(genericDefaultInReentrantCallResolution.ts, 11, 1)) +>data : Symbol(data, Decl(genericDefaultInReentrantCallResolution.ts, 15, 31)) +>v : Symbol(v, Decl(genericDefaultInReentrantCallResolution.ts, 15, 38)) + + return executeFetch("x", data); +>executeFetch : Symbol(executeFetch, Decl(genericDefaultInReentrantCallResolution.ts, 4, 76)) +>data : Symbol(data, Decl(genericDefaultInReentrantCallResolution.ts, 15, 31)) + +}, []); + +export const check: Promise = cb({ v: 1 }); +>check : Symbol(check, Decl(genericDefaultInReentrantCallResolution.ts, 19, 12)) +>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, --, --)) +>cb : Symbol(cb, Decl(genericDefaultInReentrantCallResolution.ts, 15, 12)) +>v : Symbol(v, Decl(genericDefaultInReentrantCallResolution.ts, 19, 42)) + +// https://github.com/microsoft/TypeScript/issues/63949#issuecomment-5400383233 + +declare function request( +>request : Symbol(request, Decl(genericDefaultInReentrantCallResolution.ts, 19, 51)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 23, 25)) +>input : Symbol(input, Decl(genericDefaultInReentrantCallResolution.ts, 23, 30)) +>value : Symbol(value, Decl(genericDefaultInReentrantCallResolution.ts, 23, 39)) +>output : Symbol(output, Decl(genericDefaultInReentrantCallResolution.ts, 23, 56)) + + body?: T extends { input: unknown } ? T["input"] : never, +>body : Symbol(body, Decl(genericDefaultInReentrantCallResolution.ts, 23, 75)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 23, 25)) +>input : Symbol(input, Decl(genericDefaultInReentrantCallResolution.ts, 24, 22)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 23, 25)) + +): T extends { output: unknown } ? T["output"] : T; +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 23, 25)) +>output : Symbol(output, Decl(genericDefaultInReentrantCallResolution.ts, 25, 14)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 23, 25)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 23, 25)) + +declare function identity(callback: T): T; +>identity : Symbol(identity, Decl(genericDefaultInReentrantCallResolution.ts, 25, 51)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 27, 26)) +>callback : Symbol(callback, Decl(genericDefaultInReentrantCallResolution.ts, 27, 29)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 27, 26)) +>T : Symbol(T, Decl(genericDefaultInReentrantCallResolution.ts, 27, 26)) + +identity((data: { value: number }) => +>identity : Symbol(identity, Decl(genericDefaultInReentrantCallResolution.ts, 25, 51)) +>data : Symbol(data, Decl(genericDefaultInReentrantCallResolution.ts, 29, 10)) +>value : Symbol(value, Decl(genericDefaultInReentrantCallResolution.ts, 29, 17)) + + request(data), +>request : Symbol(request, Decl(genericDefaultInReentrantCallResolution.ts, 19, 51)) +>data : Symbol(data, Decl(genericDefaultInReentrantCallResolution.ts, 29, 10)) + +); + diff --git a/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.types b/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.types new file mode 100644 index 0000000000000..6e0e5c124c6f4 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.types @@ -0,0 +1,96 @@ +//// [tests/cases/compiler/genericDefaultInReentrantCallResolution.ts] //// + +=== genericDefaultInReentrantCallResolution.ts === +// https://github.com/microsoft/TypeScript/issues/63949 + +declare function fetcher(cfg: unknown): Promise; +>fetcher : (cfg: unknown) => Promise +>cfg : unknown + +type Routes = R extends "x" ? { IN: { v: number }; OUT: string } : never; +>Routes : Routes +>IN : { v: number; } +>v : number +>OUT : string + +function executeFetch>( +>executeFetch : >(route: R, body?: T extends Record<"IN", any> ? T["IN"] : never) => Promise ? T["OUT"] : T> + + route: R, +>route : R + + body?: T extends Record<"IN", any> ? T["IN"] : never, +>body : (T extends Record<"IN", any> ? T["IN"] : never) | undefined + +) { + return fetcher ? T["OUT"] : T>({ route, body }); +>fetcher ? T["OUT"] : T>({ route, body }) : Promise ? T["OUT"] : T> +>fetcher : (cfg: unknown) => Promise +>{ route, body } : { route: R; body: (T extends Record<"IN", any> ? T["IN"] : never) | undefined; } +>route : R +>body : (T extends Record<"IN", any> ? T["IN"] : never) | undefined +} + +declare function useCallback(cb: T, deps: unknown[]): T; +>useCallback : (cb: T, deps: unknown[]) => T +>cb : T +>deps : unknown[] + +export const cb = useCallback((data: { v: number }) => { +>cb : (data: { v: number; }) => Promise +>useCallback((data: { v: number }) => { return executeFetch("x", data);}, []) : (data: { v: number; }) => Promise +>useCallback : (cb: T, deps: unknown[]) => T +>(data: { v: number }) => { return executeFetch("x", data);} : (data: { v: number; }) => Promise +>data : { v: number; } +>v : number + + return executeFetch("x", data); +>executeFetch("x", data) : Promise +>executeFetch : >(route: R, body?: T extends Record<"IN", any> ? T["IN"] : never) => Promise ? T["OUT"] : T> +>"x" : "x" +>data : { v: number; } + +}, []); +>[] : never[] + +export const check: Promise = cb({ v: 1 }); +>check : Promise +>cb({ v: 1 }) : Promise +>cb : (data: { v: number; }) => Promise +>{ v: 1 } : { v: number; } +>v : number +>1 : 1 + +// https://github.com/microsoft/TypeScript/issues/63949#issuecomment-5400383233 + +declare function request( +>request : (body?: T extends { input: unknown; } ? T["input"] : never) => T extends { output: unknown; } ? T["output"] : T +>input : { value: number; } +>value : number +>output : string + + body?: T extends { input: unknown } ? T["input"] : never, +>body : (T extends { input: unknown; } ? T["input"] : never) | undefined +>input : unknown + +): T extends { output: unknown } ? T["output"] : T; +>output : unknown + +declare function identity(callback: T): T; +>identity : (callback: T) => T +>callback : T + +identity((data: { value: number }) => +>identity((data: { value: number }) => request(data),) : (data: { value: number; }) => string +>identity : (callback: T) => T +>(data: { value: number }) => request(data) : (data: { value: number; }) => string +>data : { value: number; } +>value : number + + request(data), +>request(data) : string +>request : (body?: T extends { input: unknown; } ? T["input"] : never) => T extends { output: unknown; } ? T["output"] : T +>data : { value: number; } + +); + diff --git a/tsc/testdata/baselines/reference/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.errors.txt b/tsc/testdata/baselines/reference/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.errors.txt new file mode 100644 index 0000000000000..a6d30e87d3f05 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.errors.txt @@ -0,0 +1,16 @@ +reentrantCallDoesNotReuseInapplicableCachedSignature.ts(6,20): error TS2345: Argument of type '(a: string) => string' is not assignable to parameter of type '(a: string) => number'. + Type 'string' is not assignable to type 'number'. + + +==== reentrantCallDoesNotReuseInapplicableCachedSignature.ts (1 errors) ==== + declare const example: (f: (a: string) => number) => string; + + const f = (a: string) => g(); + + const g = () => { + return example(f); + ~ +!!! error TS2345: Argument of type '(a: string) => string' is not assignable to parameter of type '(a: string) => number'. +!!! error TS2345: Type 'string' is not assignable to type 'number'. + }; + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.symbols b/tsc/testdata/baselines/reference/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.symbols new file mode 100644 index 0000000000000..7783678d05f22 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.symbols @@ -0,0 +1,22 @@ +//// [tests/cases/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.ts] //// + +=== reentrantCallDoesNotReuseInapplicableCachedSignature.ts === +declare const example: (f: (a: string) => number) => string; +>example : Symbol(example, Decl(reentrantCallDoesNotReuseInapplicableCachedSignature.ts, 0, 13)) +>f : Symbol(f, Decl(reentrantCallDoesNotReuseInapplicableCachedSignature.ts, 0, 24)) +>a : Symbol(a, Decl(reentrantCallDoesNotReuseInapplicableCachedSignature.ts, 0, 28)) + +const f = (a: string) => g(); +>f : Symbol(f, Decl(reentrantCallDoesNotReuseInapplicableCachedSignature.ts, 2, 5)) +>a : Symbol(a, Decl(reentrantCallDoesNotReuseInapplicableCachedSignature.ts, 2, 11)) +>g : Symbol(g, Decl(reentrantCallDoesNotReuseInapplicableCachedSignature.ts, 4, 5)) + +const g = () => { +>g : Symbol(g, Decl(reentrantCallDoesNotReuseInapplicableCachedSignature.ts, 4, 5)) + + return example(f); +>example : Symbol(example, Decl(reentrantCallDoesNotReuseInapplicableCachedSignature.ts, 0, 13)) +>f : Symbol(f, Decl(reentrantCallDoesNotReuseInapplicableCachedSignature.ts, 2, 5)) + +}; + diff --git a/tsc/testdata/baselines/reference/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.types b/tsc/testdata/baselines/reference/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.types new file mode 100644 index 0000000000000..e539b3f28f01d --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.types @@ -0,0 +1,26 @@ +//// [tests/cases/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.ts] //// + +=== reentrantCallDoesNotReuseInapplicableCachedSignature.ts === +declare const example: (f: (a: string) => number) => string; +>example : (f: (a: string) => number) => string +>f : (a: string) => number +>a : string + +const f = (a: string) => g(); +>f : (a: string) => string +>(a: string) => g() : (a: string) => string +>a : string +>g() : string +>g : () => string + +const g = () => { +>g : () => string +>() => { return example(f);} : () => string + + return example(f); +>example(f) : string +>example : (f: (a: string) => number) => string +>f : (a: string) => string + +}; + diff --git a/tsc/testdata/baselines/reference/conformance/intraExpressionInferencesJsx.types b/tsc/testdata/baselines/reference/conformance/intraExpressionInferencesJsx.types index 706d73f8b21ca..9f45b75bfcf1a 100644 --- a/tsc/testdata/baselines/reference/conformance/intraExpressionInferencesJsx.types +++ b/tsc/testdata/baselines/reference/conformance/intraExpressionInferencesJsx.types @@ -143,10 +143,10 @@ const Component = ({ >func : () => { a: true; } return { ->{ a: true, } : { a: true; } +>{ a: true, } : { a: boolean; } a: true, ->a : true +>a : boolean >true : true }; @@ -192,10 +192,10 @@ const Component = ({ >() => { return { a: true, }; } : () => { a: true; } return { ->{ a: true, } : { a: true; } +>{ a: true, } : { a: boolean; } a: true, ->a : true +>a : boolean >true : true }; diff --git a/tsc/testdata/baselines/reference/conformance/neverReturningFunctions1.types b/tsc/testdata/baselines/reference/conformance/neverReturningFunctions1.types index 02bed70976bcc..f10fa31c311ad 100644 --- a/tsc/testdata/baselines/reference/conformance/neverReturningFunctions1.types +++ b/tsc/testdata/baselines/reference/conformance/neverReturningFunctions1.types @@ -591,7 +591,7 @@ const Component = registerComponent('test-component', { >parse : () => boolean[] return [true]; ->[true] : true[] +>[true] : boolean[] >true : true } }, diff --git a/tsc/testdata/tests/cases/compiler/genericDefaultInReentrantCallResolution.ts b/tsc/testdata/tests/cases/compiler/genericDefaultInReentrantCallResolution.ts new file mode 100644 index 0000000000000..de3853c82a9cf --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/genericDefaultInReentrantCallResolution.ts @@ -0,0 +1,36 @@ +// @target: es2015 +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/63949 + +declare function fetcher(cfg: unknown): Promise; + +type Routes = R extends "x" ? { IN: { v: number }; OUT: string } : never; + +function executeFetch>( + route: R, + body?: T extends Record<"IN", any> ? T["IN"] : never, +) { + return fetcher ? T["OUT"] : T>({ route, body }); +} + +declare function useCallback(cb: T, deps: unknown[]): T; + +export const cb = useCallback((data: { v: number }) => { + return executeFetch("x", data); +}, []); + +export const check: Promise = cb({ v: 1 }); + +// https://github.com/microsoft/TypeScript/issues/63949#issuecomment-5400383233 + +declare function request( + body?: T extends { input: unknown } ? T["input"] : never, +): T extends { output: unknown } ? T["output"] : T; + +declare function identity(callback: T): T; + +identity((data: { value: number }) => + request(data), +); diff --git a/tsc/testdata/tests/cases/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.ts b/tsc/testdata/tests/cases/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.ts new file mode 100644 index 0000000000000..c3b754184e878 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.ts @@ -0,0 +1,10 @@ +// @strict: true +// @noEmit: true + +declare const example: (f: (a: string) => number) => string; + +const f = (a: string) => g(); + +const g = () => { + return example(f); +};