diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 9c9cb64c78a31..0e1be1d9f2db7 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,13 @@ 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 { + // Signature resolution may reenter for the same node and cache the source-order result. + // Prefer that result over one computed using an incomplete contextual type. + debug.Assert(links.resolvedSignature != nil) + return links.resolvedSignature + } if result != nil { return result } @@ -9027,7 +9026,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/genericDefaultInContextSensitiveCallback.symbols b/tsc/testdata/baselines/reference/compiler/genericDefaultInContextSensitiveCallback.symbols new file mode 100644 index 0000000000000..cdbc2c1709522 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericDefaultInContextSensitiveCallback.symbols @@ -0,0 +1,36 @@ +//// [tests/cases/compiler/genericDefaultInContextSensitiveCallback.ts] //// + +=== genericDefaultInContextSensitiveCallback.ts === +declare function request( +>request : Symbol(request, Decl(genericDefaultInContextSensitiveCallback.ts, 0, 0)) +>T : Symbol(T, Decl(genericDefaultInContextSensitiveCallback.ts, 0, 25)) +>input : Symbol(input, Decl(genericDefaultInContextSensitiveCallback.ts, 0, 30)) +>value : Symbol(value, Decl(genericDefaultInContextSensitiveCallback.ts, 0, 39)) +>output : Symbol(output, Decl(genericDefaultInContextSensitiveCallback.ts, 0, 56)) + + body?: T extends { input: unknown } ? T["input"] : never, +>body : Symbol(body, Decl(genericDefaultInContextSensitiveCallback.ts, 0, 75)) +>T : Symbol(T, Decl(genericDefaultInContextSensitiveCallback.ts, 0, 25)) +>input : Symbol(input, Decl(genericDefaultInContextSensitiveCallback.ts, 1, 22)) +>T : Symbol(T, Decl(genericDefaultInContextSensitiveCallback.ts, 0, 25)) + +): T extends { output: unknown } ? T["output"] : T; +>T : Symbol(T, Decl(genericDefaultInContextSensitiveCallback.ts, 0, 25)) +>output : Symbol(output, Decl(genericDefaultInContextSensitiveCallback.ts, 2, 14)) +>T : Symbol(T, Decl(genericDefaultInContextSensitiveCallback.ts, 0, 25)) +>T : Symbol(T, Decl(genericDefaultInContextSensitiveCallback.ts, 0, 25)) + +declare function identity(callback: T): T; +>identity : Symbol(identity, Decl(genericDefaultInContextSensitiveCallback.ts, 2, 51)) +>T : Symbol(T, Decl(genericDefaultInContextSensitiveCallback.ts, 4, 26)) +>callback : Symbol(callback, Decl(genericDefaultInContextSensitiveCallback.ts, 4, 29)) +>T : Symbol(T, Decl(genericDefaultInContextSensitiveCallback.ts, 4, 26)) +>T : Symbol(T, Decl(genericDefaultInContextSensitiveCallback.ts, 4, 26)) + +identity((data: { value: number }) => request(data)); +>identity : Symbol(identity, Decl(genericDefaultInContextSensitiveCallback.ts, 2, 51)) +>data : Symbol(data, Decl(genericDefaultInContextSensitiveCallback.ts, 6, 10)) +>value : Symbol(value, Decl(genericDefaultInContextSensitiveCallback.ts, 6, 17)) +>request : Symbol(request, Decl(genericDefaultInContextSensitiveCallback.ts, 0, 0)) +>data : Symbol(data, Decl(genericDefaultInContextSensitiveCallback.ts, 6, 10)) + diff --git a/tsc/testdata/baselines/reference/compiler/genericDefaultInContextSensitiveCallback.types b/tsc/testdata/baselines/reference/compiler/genericDefaultInContextSensitiveCallback.types new file mode 100644 index 0000000000000..83f100a12be8d --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericDefaultInContextSensitiveCallback.types @@ -0,0 +1,30 @@ +//// [tests/cases/compiler/genericDefaultInContextSensitiveCallback.ts] //// + +=== genericDefaultInContextSensitiveCallback.ts === +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 }) => request(data)); +>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) : 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/genericDefaultInContextSensitiveCallback.ts b/tsc/testdata/tests/cases/compiler/genericDefaultInContextSensitiveCallback.ts new file mode 100644 index 0000000000000..6876745cd4da8 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/genericDefaultInContextSensitiveCallback.ts @@ -0,0 +1,10 @@ +// @strict: true +// @noEmit: true + +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));