Skip to content

Commit 3118e81

Browse files
authored
Merge pull request #17634 from Microsoft/fixSignatureStackOverflow
Fix stack overflow related to contextual signature instantiations
2 parents a453eff + a4a37ea commit 3118e81

7 files changed

+130
-21
lines changed

src/compiler/checker.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8074,7 +8074,7 @@ namespace ts {
80748074

80758075
function cloneTypeMapper(mapper: TypeMapper): TypeMapper {
80768076
return mapper && isInferenceContext(mapper) ?
8077-
createInferenceContext(mapper.signature, mapper.flags | InferenceFlags.NoDefault, mapper.inferences) :
8077+
createInferenceContext(mapper.signature, mapper.flags | InferenceFlags.NoDefault, mapper.compareTypes, mapper.inferences) :
80788078
mapper;
80798079
}
80808080

@@ -8515,7 +8515,7 @@ namespace ts {
85158515
ignoreReturnTypes: boolean,
85168516
reportErrors: boolean,
85178517
errorReporter: ErrorReporter,
8518-
compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary): Ternary {
8518+
compareTypes: TypeComparer): Ternary {
85198519
// TODO (drosen): De-duplicate code between related functions.
85208520
if (source === target) {
85218521
return Ternary.True;
@@ -8525,7 +8525,7 @@ namespace ts {
85258525
}
85268526

85278527
if (source.typeParameters) {
8528-
source = instantiateSignatureInContextOf(source, target);
8528+
source = instantiateSignatureInContextOf(source, target, /*contextualMapper*/ undefined, compareTypes);
85298529
}
85308530

85318531
let result = Ternary.True;
@@ -10273,13 +10273,14 @@ namespace ts {
1027310273
}
1027410274
}
1027510275

10276-
function createInferenceContext(signature: Signature, flags: InferenceFlags, baseInferences?: InferenceInfo[]): InferenceContext {
10276+
function createInferenceContext(signature: Signature, flags: InferenceFlags, compareTypes?: TypeComparer, baseInferences?: InferenceInfo[]): InferenceContext {
1027710277
const inferences = baseInferences ? map(baseInferences, cloneInferenceInfo) : map(signature.typeParameters, createInferenceInfo);
1027810278
const context = mapper as InferenceContext;
1027910279
context.mappedTypes = signature.typeParameters;
1028010280
context.signature = signature;
1028110281
context.inferences = inferences;
1028210282
context.flags = flags;
10283+
context.compareTypes = compareTypes || compareTypesAssignable;
1028310284
return context;
1028410285

1028510286
function mapper(t: Type): Type {
@@ -10727,7 +10728,7 @@ namespace ts {
1072710728
const constraint = getConstraintOfTypeParameter(context.signature.typeParameters[index]);
1072810729
if (constraint) {
1072910730
const instantiatedConstraint = instantiateType(constraint, context);
10730-
if (!isTypeAssignableTo(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
10731+
if (!context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
1073110732
inference.inferredType = inferredType = instantiatedConstraint;
1073210733
}
1073310734
}
@@ -15128,8 +15129,8 @@ namespace ts {
1512815129
}
1512915130

1513015131
// Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec)
15131-
function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper?: TypeMapper): Signature {
15132-
const context = createInferenceContext(signature, InferenceFlags.InferUnionTypes);
15132+
function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper?: TypeMapper, compareTypes?: TypeComparer): Signature {
15133+
const context = createInferenceContext(signature, InferenceFlags.InferUnionTypes, compareTypes);
1513315134
forEachMatchingParameterType(contextualSignature, signature, (source, target) => {
1513415135
// Type parameters from outer context referenced by source type are fixed by instantiation of the source type
1513515136
inferTypes(context.inferences, instantiateType(source, contextualMapper || identityMapper), target);

src/compiler/core.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,6 @@ namespace ts {
1111

1212
/* @internal */
1313
namespace ts {
14-
/**
15-
* Ternary values are defined such that
16-
* x & y is False if either x or y is False.
17-
* x & y is Maybe if either x or y is Maybe, but neither x or y is False.
18-
* x & y is True if both x and y are True.
19-
* x | y is False if both x and y are False.
20-
* x | y is Maybe if either x or y is Maybe, but neither x or y is True.
21-
* x | y is True if either x or y is True.
22-
*/
23-
export const enum Ternary {
24-
False = 0,
25-
Maybe = 1,
26-
True = -1
27-
}
2814

2915
// More efficient to create a collator once and use its `compare` than to call `a.localeCompare(b)` many times.
3016
export const collator: { compare(a: string, b: string): number } = typeof Intl === "object" && typeof Intl.Collator === "function" ? new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "accent" }) : undefined;

src/compiler/types.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3425,11 +3425,29 @@ namespace ts {
34253425
AnyDefault = 1 << 2, // Infer anyType for no inferences (otherwise emptyObjectType)
34263426
}
34273427

3428+
/**
3429+
* Ternary values are defined such that
3430+
* x & y is False if either x or y is False.
3431+
* x & y is Maybe if either x or y is Maybe, but neither x or y is False.
3432+
* x & y is True if both x and y are True.
3433+
* x | y is False if both x and y are False.
3434+
* x | y is Maybe if either x or y is Maybe, but neither x or y is True.
3435+
* x | y is True if either x or y is True.
3436+
*/
3437+
export const enum Ternary {
3438+
False = 0,
3439+
Maybe = 1,
3440+
True = -1
3441+
}
3442+
3443+
export type TypeComparer = (s: Type, t: Type, reportErrors?: boolean) => Ternary;
3444+
34283445
/* @internal */
34293446
export interface InferenceContext extends TypeMapper {
34303447
signature: Signature; // Generic signature for which inferences are made
34313448
inferences: InferenceInfo[]; // Inferences made for each type parameter
34323449
flags: InferenceFlags; // Inference flags
3450+
compareTypes: TypeComparer; // Type comparer function
34333451
}
34343452

34353453
/* @internal */
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//// [signatureInstantiationWithRecursiveConstraints.ts]
2+
// Repro from #17148
3+
4+
class Foo {
5+
myFunc<T extends Foo>(arg: T) {}
6+
}
7+
8+
class Bar {
9+
myFunc<T extends Bar>(arg: T) {}
10+
}
11+
12+
const myVar: Foo = new Bar();
13+
14+
15+
//// [signatureInstantiationWithRecursiveConstraints.js]
16+
"use strict";
17+
// Repro from #17148
18+
var Foo = (function () {
19+
function Foo() {
20+
}
21+
Foo.prototype.myFunc = function (arg) { };
22+
return Foo;
23+
}());
24+
var Bar = (function () {
25+
function Bar() {
26+
}
27+
Bar.prototype.myFunc = function (arg) { };
28+
return Bar;
29+
}());
30+
var myVar = new Bar();
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
=== tests/cases/compiler/signatureInstantiationWithRecursiveConstraints.ts ===
2+
// Repro from #17148
3+
4+
class Foo {
5+
>Foo : Symbol(Foo, Decl(signatureInstantiationWithRecursiveConstraints.ts, 0, 0))
6+
7+
myFunc<T extends Foo>(arg: T) {}
8+
>myFunc : Symbol(Foo.myFunc, Decl(signatureInstantiationWithRecursiveConstraints.ts, 2, 11))
9+
>T : Symbol(T, Decl(signatureInstantiationWithRecursiveConstraints.ts, 3, 9))
10+
>Foo : Symbol(Foo, Decl(signatureInstantiationWithRecursiveConstraints.ts, 0, 0))
11+
>arg : Symbol(arg, Decl(signatureInstantiationWithRecursiveConstraints.ts, 3, 24))
12+
>T : Symbol(T, Decl(signatureInstantiationWithRecursiveConstraints.ts, 3, 9))
13+
}
14+
15+
class Bar {
16+
>Bar : Symbol(Bar, Decl(signatureInstantiationWithRecursiveConstraints.ts, 4, 1))
17+
18+
myFunc<T extends Bar>(arg: T) {}
19+
>myFunc : Symbol(Bar.myFunc, Decl(signatureInstantiationWithRecursiveConstraints.ts, 6, 11))
20+
>T : Symbol(T, Decl(signatureInstantiationWithRecursiveConstraints.ts, 7, 9))
21+
>Bar : Symbol(Bar, Decl(signatureInstantiationWithRecursiveConstraints.ts, 4, 1))
22+
>arg : Symbol(arg, Decl(signatureInstantiationWithRecursiveConstraints.ts, 7, 24))
23+
>T : Symbol(T, Decl(signatureInstantiationWithRecursiveConstraints.ts, 7, 9))
24+
}
25+
26+
const myVar: Foo = new Bar();
27+
>myVar : Symbol(myVar, Decl(signatureInstantiationWithRecursiveConstraints.ts, 10, 5))
28+
>Foo : Symbol(Foo, Decl(signatureInstantiationWithRecursiveConstraints.ts, 0, 0))
29+
>Bar : Symbol(Bar, Decl(signatureInstantiationWithRecursiveConstraints.ts, 4, 1))
30+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
=== tests/cases/compiler/signatureInstantiationWithRecursiveConstraints.ts ===
2+
// Repro from #17148
3+
4+
class Foo {
5+
>Foo : Foo
6+
7+
myFunc<T extends Foo>(arg: T) {}
8+
>myFunc : <T extends Foo>(arg: T) => void
9+
>T : T
10+
>Foo : Foo
11+
>arg : T
12+
>T : T
13+
}
14+
15+
class Bar {
16+
>Bar : Bar
17+
18+
myFunc<T extends Bar>(arg: T) {}
19+
>myFunc : <T extends Bar>(arg: T) => void
20+
>T : T
21+
>Bar : Bar
22+
>arg : T
23+
>T : T
24+
}
25+
26+
const myVar: Foo = new Bar();
27+
>myVar : Foo
28+
>Foo : Foo
29+
>new Bar() : Bar
30+
>Bar : typeof Bar
31+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// @strict: true
2+
3+
// Repro from #17148
4+
5+
class Foo {
6+
myFunc<T extends Foo>(arg: T) {}
7+
}
8+
9+
class Bar {
10+
myFunc<T extends Bar>(arg: T) {}
11+
}
12+
13+
const myVar: Foo = new Bar();

0 commit comments

Comments
 (0)