Skip to content

Commit 5e2e321

Browse files
authored
Reuse cached resolved signatures early (#60208)
1 parent 8d95ac5 commit 5e2e321

File tree

5 files changed

+132
-11
lines changed

5 files changed

+132
-11
lines changed

src/compiler/checker.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35846,10 +35846,28 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3584635846
if (!result) {
3584735847
result = chooseOverload(candidates, assignableRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma);
3584835848
}
35849+
const links = getNodeLinks(node);
35850+
if (links.resolvedSignature !== resolvingSignature && !candidatesOutArray) {
35851+
// There are 2 situations in which it's good to preemptively return the cached result here:
35852+
//
35853+
// 1. if the signature resolution originated on a node that itself depends on the contextual type
35854+
// then it's possible that the resolved signature might not be the same as the one that would be computed in source order
35855+
// since resolving such signature leads to resolving the potential outer signature, its arguments and thus the very same signature
35856+
// it's possible that this inner resolution sets the resolvedSignature first.
35857+
// In such a case we ignore the local result and reuse the correct one that was cached.
35858+
//
35859+
// 2. In certain circular-like situations it's possible that the compiler reentries this function for the same node.
35860+
// It's possible to resolve the inner call against preemptively set empty members (for example in `resolveAnonymousTypeMembers`) of some type.
35861+
// 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.
35862+
// This in turn creates a situation in which the outer call fails in `getSignatureApplicabilityError` due to a cached `RelationComparisonResult.Failed`
35863+
// 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.
35864+
// 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.
35865+
Debug.assert(links.resolvedSignature);
35866+
return links.resolvedSignature;
35867+
}
3584935868
if (result) {
3585035869
return result;
3585135870
}
35852-
3585335871
result = getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray, checkMode);
3585435872
// Preemptively cache the result; getResolvedSignature will do this after we return, but
3585535873
// we need to ensure that the result is present for the error checks below so that if
@@ -35858,7 +35876,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3585835876
// don't hit this issue because they only observe this result after it's had a chance to
3585935877
// be cached, but the error reporting code below executes before getResolvedSignature sets
3586035878
// resolvedSignature.
35861-
getNodeLinks(node).resolvedSignature = result;
35879+
links.resolvedSignature = result;
3586235880

3586335881
// No signatures were applicable. Now report errors based on the last applicable signature with
3586435882
// no arguments excluded from assignability checks.
@@ -36871,19 +36889,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3687136889
resolutionStart = resolutionTargets.length;
3687236890
}
3687336891
links.resolvedSignature = resolvingSignature;
36874-
let result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal);
36892+
const result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal);
3687536893
resolutionStart = saveResolutionStart;
3687636894
// When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call
3687736895
// resolution should be deferred.
3687836896
if (result !== resolvingSignature) {
36879-
// if the signature resolution originated on a node that itself depends on the contextual type
36880-
// then it's possible that the resolved signature might not be the same as the one that would be computed in source order
36881-
// since resolving such signature leads to resolving the potential outer signature, its arguments and thus the very same signature
36882-
// it's possible that this inner resolution sets the resolvedSignature first.
36883-
// In such a case we ignore the local result and reuse the correct one that was cached.
36884-
if (links.resolvedSignature !== resolvingSignature) {
36885-
result = links.resolvedSignature;
36886-
}
3688736897
// If signature resolution originated in control flow type analysis (for example to compute the
3688836898
// assigned type in a flow assignment) we don't cache the result as it may be based on temporary
3688936899
// types from the control flow analysis.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
mixinWithBaseDependingOnSelfNoCrash1.ts(11,48): error TS2345: Argument of type 'typeof BaseItem' is not assignable to parameter of type 'new (...args: any[]) => any'.
2+
Type 'typeof BaseItem' provides no match for the signature 'new (...args: any[]): any'.
3+
4+
5+
==== mixinWithBaseDependingOnSelfNoCrash1.ts (1 errors) ====
6+
// https://github.com/microsoft/TypeScript/issues/60202
7+
8+
declare class Document<Parent> {}
9+
10+
declare class BaseItem extends Document<typeof Item> {}
11+
12+
declare function ClientDocumentMixin<
13+
BaseClass extends new (...args: any[]) => any,
14+
>(Base: BaseClass): any;
15+
16+
declare class Item extends ClientDocumentMixin(BaseItem) {}
17+
~~~~~~~~
18+
!!! error TS2345: Argument of type 'typeof BaseItem' is not assignable to parameter of type 'new (...args: any[]) => any'.
19+
!!! error TS2345: Type 'typeof BaseItem' provides no match for the signature 'new (...args: any[]): any'.
20+
21+
export {};
22+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//// [tests/cases/conformance/classes/mixinWithBaseDependingOnSelfNoCrash1.ts] ////
2+
3+
=== mixinWithBaseDependingOnSelfNoCrash1.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/60202
5+
6+
declare class Document<Parent> {}
7+
>Document : Symbol(Document, Decl(mixinWithBaseDependingOnSelfNoCrash1.ts, 0, 0))
8+
>Parent : Symbol(Parent, Decl(mixinWithBaseDependingOnSelfNoCrash1.ts, 2, 23))
9+
10+
declare class BaseItem extends Document<typeof Item> {}
11+
>BaseItem : Symbol(BaseItem, Decl(mixinWithBaseDependingOnSelfNoCrash1.ts, 2, 33))
12+
>Document : Symbol(Document, Decl(mixinWithBaseDependingOnSelfNoCrash1.ts, 0, 0))
13+
>Item : Symbol(Item, Decl(mixinWithBaseDependingOnSelfNoCrash1.ts, 8, 24))
14+
15+
declare function ClientDocumentMixin<
16+
>ClientDocumentMixin : Symbol(ClientDocumentMixin, Decl(mixinWithBaseDependingOnSelfNoCrash1.ts, 4, 55))
17+
18+
BaseClass extends new (...args: any[]) => any,
19+
>BaseClass : Symbol(BaseClass, Decl(mixinWithBaseDependingOnSelfNoCrash1.ts, 6, 37))
20+
>args : Symbol(args, Decl(mixinWithBaseDependingOnSelfNoCrash1.ts, 7, 25))
21+
22+
>(Base: BaseClass): any;
23+
>Base : Symbol(Base, Decl(mixinWithBaseDependingOnSelfNoCrash1.ts, 8, 2))
24+
>BaseClass : Symbol(BaseClass, Decl(mixinWithBaseDependingOnSelfNoCrash1.ts, 6, 37))
25+
26+
declare class Item extends ClientDocumentMixin(BaseItem) {}
27+
>Item : Symbol(Item, Decl(mixinWithBaseDependingOnSelfNoCrash1.ts, 8, 24))
28+
>ClientDocumentMixin : Symbol(ClientDocumentMixin, Decl(mixinWithBaseDependingOnSelfNoCrash1.ts, 4, 55))
29+
>BaseItem : Symbol(BaseItem, Decl(mixinWithBaseDependingOnSelfNoCrash1.ts, 2, 33))
30+
31+
export {};
32+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//// [tests/cases/conformance/classes/mixinWithBaseDependingOnSelfNoCrash1.ts] ////
2+
3+
=== mixinWithBaseDependingOnSelfNoCrash1.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/60202
5+
6+
declare class Document<Parent> {}
7+
>Document : Document<Parent>
8+
> : ^^^^^^^^^^^^^^^^
9+
10+
declare class BaseItem extends Document<typeof Item> {}
11+
>BaseItem : BaseItem
12+
> : ^^^^^^^^
13+
>Document : Document<typeof Item>
14+
> : ^^^^^^^^^^^^^^^^^^^^^
15+
>Item : typeof Item
16+
> : ^^^^^^^^^^^
17+
18+
declare function ClientDocumentMixin<
19+
>ClientDocumentMixin : <BaseClass extends new (...args: any[]) => any>(Base: BaseClass) => any
20+
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
21+
22+
BaseClass extends new (...args: any[]) => any,
23+
>args : any[]
24+
> : ^^^^^
25+
26+
>(Base: BaseClass): any;
27+
>Base : BaseClass
28+
> : ^^^^^^^^^
29+
30+
declare class Item extends ClientDocumentMixin(BaseItem) {}
31+
>Item : Item
32+
> : ^^^^
33+
>ClientDocumentMixin(BaseItem) : any
34+
> : ^^^
35+
>ClientDocumentMixin : <BaseClass extends new (...args: any[]) => any>(Base: BaseClass) => any
36+
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
37+
>BaseItem : typeof BaseItem
38+
> : ^^^^^^^^^^^^^^^
39+
40+
export {};
41+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
// https://github.com/microsoft/TypeScript/issues/60202
5+
6+
declare class Document<Parent> {}
7+
8+
declare class BaseItem extends Document<typeof Item> {}
9+
10+
declare function ClientDocumentMixin<
11+
BaseClass extends new (...args: any[]) => any,
12+
>(Base: BaseClass): any;
13+
14+
declare class Item extends ClientDocumentMixin(BaseItem) {}
15+
16+
export {};

0 commit comments

Comments
 (0)