From 6705198259c7aaf8e4f8f8358902532c10079b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 23 Aug 2026 09:45:21 +0200 Subject: [PATCH 1/3] Port "Reuse cached resolved signatures early" --- tsc/internal/checker/checker.go | 29 ++++--- ...icDefaultInReentrantCallResolution.symbols | 75 +++++++++++++++++++ ...ericDefaultInReentrantCallResolution.types | 63 ++++++++++++++++ ...genericDefaultInReentrantCallResolution.ts | 24 ++++++ 4 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.types create mode 100644 tsc/testdata/tests/cases/compiler/genericDefaultInReentrantCallResolution.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 2a4ead5003a89..619b5c4ed33d0 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -8520,14 +8520,6 @@ func (c *Checker) getResolvedSignature(node *ast.Node, candidatesOutArray *[]*Si // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call // resolution should be deferred. if result != c.resolvingSignature { - // if the signature resolution originated on a node that itself depends on the contextual type - // then it's possible that the resolved signature might not be the same as the one that would be computed in source order - // since resolving such signature leads to resolving the potential outer signature, its arguments and thus the very same signature - // it's possible that this inner resolution sets the resolvedSignature first. - // In such a case we ignore the local result and reuse the correct one that was cached. - if links.resolvedSignature != c.resolvingSignature { - result = links.resolvedSignature - } // If signature resolution originated in control flow type analysis (for example to compute the // assigned type in a flow assignment) we don't cache the result as it may be based on temporary // types from the control flow analysis. @@ -9016,6 +9008,25 @@ func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidate if result == nil { result = c.chooseOverload(&s, c.assignableRelation) } + links := c.signatureLinks.Get(node) + if links.resolvedSignature != c.resolvingSignature && candidatesOutArray == nil { + // There are 2 situations in which it's good to preemptively return the cached result here: + // + // 1. if the signature resolution originated on a node that itself depends on the contextual type + // then it's possible that the resolved signature might not be the same as the one that would be computed in source order + // since resolving such signature leads to resolving the potential outer signature, its arguments and thus the very same signature + // it's possible that this inner resolution sets the resolvedSignature first. + // In such a case we ignore the local result and reuse the correct one that was cached. + // + // 2. In certain circular-like situations it's possible that the compiler reentries this function for the same node. + // It's possible to resolve the inner call against preemptively set empty members (for example in `resolveAnonymousTypeMembers`) of some type. + // When that happens the compiler might report an error for that inner call but at the same time it might end up resolving the actual members of the other type. + // This in turn creates a situation in which the outer call fails in `getSignatureApplicabilityError` due to a cached `RelationComparisonResult.Failed` + // but when the compiler tries to report that error (in the code below) it also tries to elaborate it and that can succeed as types would be related against the *resolved* members of the other type. + // This can hit `No error for last overload signature` assert but since that error was already reported when the inner call failed we can skip this step altogether here by returning the cached signature early. + debug.Assert(links.resolvedSignature != nil) + return links.resolvedSignature + } if result != nil { return result } @@ -9027,7 +9038,7 @@ func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidate // don't hit this issue because they only observe this result after it's had a chance to // be cached, but the error reporting code below executes before getResolvedSignature sets // resolvedSignature. - c.signatureLinks.Get(node).resolvedSignature = result + links.resolvedSignature = result // No signatures were applicable. Now report errors based on the last applicable signature with // no arguments excluded from assignability checks. // If candidate is undefined, it means that no candidates had a suitable arity. In that case, diff --git a/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.symbols b/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.symbols new file mode 100644 index 0000000000000..f6b09ee432b5d --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.symbols @@ -0,0 +1,75 @@ +//// [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)) + diff --git a/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.types b/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.types new file mode 100644 index 0000000000000..25fe5a95a4358 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.types @@ -0,0 +1,63 @@ +//// [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 + diff --git a/tsc/testdata/tests/cases/compiler/genericDefaultInReentrantCallResolution.ts b/tsc/testdata/tests/cases/compiler/genericDefaultInReentrantCallResolution.ts new file mode 100644 index 0000000000000..8f4af5fb43385 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/genericDefaultInReentrantCallResolution.ts @@ -0,0 +1,24 @@ +// @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 }); From 2198dcc527f0848047f6d190729127db0b65508f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 28 Aug 2026 17:30:13 +0200 Subject: [PATCH 2/3] Prevent self-contextual return inference and restore diagnostics for inapplicable reentrant calls. --- tsc/internal/checker/checker.go | 32 +++++-------- ...icDefaultInReentrantCallResolution_test.go | 48 +++++++++++++++++++ ...euseInapplicableCachedSignature.errors.txt | 16 +++++++ ...otReuseInapplicableCachedSignature.symbols | 22 +++++++++ ...sNotReuseInapplicableCachedSignature.types | 26 ++++++++++ .../intraExpressionInferencesJsx.types | 8 ++-- .../neverReturningFunctions1.types | 2 +- ...DoesNotReuseInapplicableCachedSignature.ts | 10 ++++ 8 files changed, 138 insertions(+), 26 deletions(-) create mode 100644 tsc/internal/fourslash/tests/quickInfoGenericDefaultInReentrantCallResolution_test.go create mode 100644 tsc/testdata/baselines/reference/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.types create mode 100644 tsc/testdata/tests/cases/compiler/reentrantCallDoesNotReuseInapplicableCachedSignature.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 619b5c4ed33d0..c330f317791b5 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -8520,6 +8520,14 @@ func (c *Checker) getResolvedSignature(node *ast.Node, candidatesOutArray *[]*Si // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call // resolution should be deferred. if result != c.resolvingSignature { + // if the signature resolution originated on a node that itself depends on the contextual type + // then it's possible that the resolved signature might not be the same as the one that would be computed in source order + // since resolving such signature leads to resolving the potential outer signature, its arguments and thus the very same signature + // it's possible that this inner resolution sets the resolvedSignature first. + // In such a case we ignore the local result and reuse the correct one that was cached. + if links.resolvedSignature != c.resolvingSignature { + result = links.resolvedSignature + } // If signature resolution originated in control flow type analysis (for example to compute the // assigned type in a flow assignment) we don't cache the result as it may be based on temporary // types from the control flow analysis. @@ -9008,25 +9016,6 @@ func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidate if result == nil { result = c.chooseOverload(&s, c.assignableRelation) } - links := c.signatureLinks.Get(node) - if links.resolvedSignature != c.resolvingSignature && candidatesOutArray == nil { - // There are 2 situations in which it's good to preemptively return the cached result here: - // - // 1. if the signature resolution originated on a node that itself depends on the contextual type - // then it's possible that the resolved signature might not be the same as the one that would be computed in source order - // since resolving such signature leads to resolving the potential outer signature, its arguments and thus the very same signature - // it's possible that this inner resolution sets the resolvedSignature first. - // In such a case we ignore the local result and reuse the correct one that was cached. - // - // 2. In certain circular-like situations it's possible that the compiler reentries this function for the same node. - // It's possible to resolve the inner call against preemptively set empty members (for example in `resolveAnonymousTypeMembers`) of some type. - // When that happens the compiler might report an error for that inner call but at the same time it might end up resolving the actual members of the other type. - // This in turn creates a situation in which the outer call fails in `getSignatureApplicabilityError` due to a cached `RelationComparisonResult.Failed` - // but when the compiler tries to report that error (in the code below) it also tries to elaborate it and that can succeed as types would be related against the *resolved* members of the other type. - // This can hit `No error for last overload signature` assert but since that error was already reported when the inner call failed we can skip this step altogether here by returning the cached signature early. - debug.Assert(links.resolvedSignature != nil) - return links.resolvedSignature - } if result != nil { return result } @@ -9038,7 +9027,7 @@ func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidate // don't hit this issue because they only observe this result after it's had a chance to // be cached, but the error reporting code below executes before getResolvedSignature sets // resolvedSignature. - links.resolvedSignature = result + c.signatureLinks.Get(node).resolvedSignature = result // No signatures were applicable. Now report errors based on the last applicable signature with // no arguments excluded from assignability checks. // If candidate is undefined, it means that no candidates had a suitable arity. In that case, @@ -29806,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/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/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); +}; From 00c20758cbc0ecc080bd9f0ea53c8a78e918fbe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 28 Aug 2026 19:04:46 +0200 Subject: [PATCH 3/3] add extra test --- ...icDefaultInReentrantCallResolution.symbols | 39 +++++++++++++++++++ ...ericDefaultInReentrantCallResolution.types | 33 ++++++++++++++++ ...genericDefaultInReentrantCallResolution.ts | 12 ++++++ 3 files changed, 84 insertions(+) diff --git a/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.symbols b/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.symbols index f6b09ee432b5d..29042053067da 100644 --- a/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.symbols +++ b/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.symbols @@ -73,3 +73,42 @@ export const check: Promise = cb({ v: 1 }); >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 index 25fe5a95a4358..6e0e5c124c6f4 100644 --- a/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.types +++ b/tsc/testdata/baselines/reference/compiler/genericDefaultInReentrantCallResolution.types @@ -61,3 +61,36 @@ export const check: Promise = cb({ v: 1 }); >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/tests/cases/compiler/genericDefaultInReentrantCallResolution.ts b/tsc/testdata/tests/cases/compiler/genericDefaultInReentrantCallResolution.ts index 8f4af5fb43385..de3853c82a9cf 100644 --- a/tsc/testdata/tests/cases/compiler/genericDefaultInReentrantCallResolution.ts +++ b/tsc/testdata/tests/cases/compiler/genericDefaultInReentrantCallResolution.ts @@ -22,3 +22,15 @@ export const cb = useCallback((data: { v: number }) => { }, []); 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), +);