Skip to content

Commit 93c76cb

Browse files
authored
Merge pull request #26558 from Microsoft/fixInfiniteConstraints
Fix infinite constraints
2 parents 7ec98af + 2f85af8 commit 93c76cb

File tree

7 files changed

+453
-61
lines changed

7 files changed

+453
-61
lines changed

src/compiler/checker.ts

Lines changed: 79 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ namespace ts {
5353
let typeCount = 0;
5454
let symbolCount = 0;
5555
let enumCount = 0;
56-
let symbolInstantiationDepth = 0;
56+
let instantiationDepth = 0;
57+
let constraintDepth = 0;
5758

5859
const emptySymbols = createSymbolTable();
5960
const identityMapper: (type: Type) => Type = identity;
@@ -5303,22 +5304,14 @@ namespace ts {
53035304
function getTypeOfInstantiatedSymbol(symbol: Symbol): Type {
53045305
const links = getSymbolLinks(symbol);
53055306
if (!links.type) {
5306-
if (symbolInstantiationDepth === 100) {
5307-
error(symbol.valueDeclaration, Diagnostics.Generic_type_instantiation_is_excessively_deep_and_possibly_infinite);
5308-
links.type = errorType;
5307+
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
5308+
return links.type = errorType;
53095309
}
5310-
else {
5311-
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
5312-
return links.type = errorType;
5313-
}
5314-
symbolInstantiationDepth++;
5315-
let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper!);
5316-
symbolInstantiationDepth--;
5317-
if (!popTypeResolution()) {
5318-
type = reportCircularityError(symbol);
5319-
}
5320-
links.type = type;
5310+
let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper!);
5311+
if (!popTypeResolution()) {
5312+
type = reportCircularityError(symbol);
53215313
}
5314+
links.type = type;
53225315
}
53235316
return links.type;
53245317
}
@@ -6928,7 +6921,8 @@ namespace ts {
69286921
// over the conditional type and possibly reduced. For example, 'T extends undefined ? never : T'
69296922
// removes 'undefined' from T.
69306923
if (type.root.isDistributive) {
6931-
const constraint = getConstraintOfType(getSimplifiedType(type.checkType));
6924+
const simplified = getSimplifiedType(type.checkType);
6925+
const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified;
69326926
if (constraint) {
69336927
const mapper = makeUnaryTypeMapper(type.root.checkType, constraint);
69346928
const instantiated = getConditionalTypeInstantiation(type, combineTypeMappers(mapper, type.mapper));
@@ -7011,6 +7005,7 @@ namespace ts {
70117005
* circularly references the type variable.
70127006
*/
70137007
function getResolvedBaseConstraint(type: InstantiableType | UnionOrIntersectionType): Type {
7008+
let nonTerminating = false;
70147009
return type.resolvedBaseConstraint ||
70157010
(type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type));
70167011

@@ -7019,8 +7014,18 @@ namespace ts {
70197014
if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) {
70207015
return circularConstraintType;
70217016
}
7017+
if (constraintDepth === 50) {
7018+
// We have reached 50 recursive invocations of getImmediateBaseConstraint and there is a
7019+
// very high likelyhood we're dealing with an infinite generic type that perpetually generates
7020+
// new type identities as we descend into it. We stop the recursion here and mark this type
7021+
// and the outer types as having circular constraints.
7022+
nonTerminating = true;
7023+
return t.immediateBaseConstraint = noConstraintType;
7024+
}
7025+
constraintDepth++;
70227026
let result = computeBaseConstraint(getSimplifiedType(t));
7023-
if (!popTypeResolution()) {
7027+
constraintDepth--;
7028+
if (!popTypeResolution() || nonTerminating) {
70247029
result = circularConstraintType;
70257030
}
70267031
t.immediateBaseConstraint = result || noConstraintType;
@@ -10293,49 +10298,66 @@ namespace ts {
1029310298
function instantiateType(type: Type, mapper: TypeMapper | undefined): Type;
1029410299
function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined;
1029510300
function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined {
10296-
if (type && mapper && mapper !== identityMapper) {
10297-
if (type.flags & TypeFlags.TypeParameter) {
10298-
return mapper(<TypeParameter>type);
10299-
}
10300-
if (type.flags & TypeFlags.Object) {
10301-
if ((<ObjectType>type).objectFlags & ObjectFlags.Anonymous) {
10302-
// If the anonymous type originates in a declaration of a function, method, class, or
10303-
// interface, in an object type literal, or in an object literal expression, we may need
10304-
// to instantiate the type because it might reference a type parameter.
10305-
return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ?
10306-
getAnonymousTypeInstantiation(<AnonymousType>type, mapper) : type;
10307-
}
10308-
if ((<ObjectType>type).objectFlags & ObjectFlags.Mapped) {
10309-
return getAnonymousTypeInstantiation(<MappedType>type, mapper);
10310-
}
10311-
if ((<ObjectType>type).objectFlags & ObjectFlags.Reference) {
10312-
const typeArguments = (<TypeReference>type).typeArguments;
10313-
const newTypeArguments = instantiateTypes(typeArguments, mapper);
10314-
return newTypeArguments !== typeArguments ? createTypeReference((<TypeReference>type).target, newTypeArguments) : type;
10315-
}
10316-
}
10317-
if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) {
10318-
const types = (<UnionType>type).types;
10319-
const newTypes = instantiateTypes(types, mapper);
10320-
return newTypes !== types ? getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type;
10321-
}
10322-
if (type.flags & TypeFlags.Intersection) {
10323-
const types = (<IntersectionType>type).types;
10324-
const newTypes = instantiateTypes(types, mapper);
10325-
return newTypes !== types ? getIntersectionType(newTypes, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type;
10326-
}
10327-
if (type.flags & TypeFlags.Index) {
10328-
return getIndexType(instantiateType((<IndexType>type).type, mapper));
10329-
}
10330-
if (type.flags & TypeFlags.IndexedAccess) {
10331-
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
10301+
if (!type || !mapper || mapper === identityMapper) {
10302+
return type;
10303+
}
10304+
if (instantiationDepth === 50) {
10305+
// We have reached 50 recursive type instantiations and there is a very high likelyhood we're dealing
10306+
// with a combination of infinite generic types that perpetually generate new type identities. We stop
10307+
// the recursion here by yielding the error type.
10308+
return errorType;
10309+
}
10310+
instantiationDepth++;
10311+
const result = instantiateTypeWorker(type, mapper);
10312+
instantiationDepth--;
10313+
return result;
10314+
}
10315+
10316+
function instantiateTypeWorker(type: Type, mapper: TypeMapper): Type {
10317+
const flags = type.flags;
10318+
if (flags & TypeFlags.TypeParameter) {
10319+
return mapper(type);
10320+
}
10321+
if (flags & TypeFlags.Object) {
10322+
const objectFlags = (<ObjectType>type).objectFlags;
10323+
if (objectFlags & ObjectFlags.Anonymous) {
10324+
// If the anonymous type originates in a declaration of a function, method, class, or
10325+
// interface, in an object type literal, or in an object literal expression, we may need
10326+
// to instantiate the type because it might reference a type parameter.
10327+
return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ?
10328+
getAnonymousTypeInstantiation(<AnonymousType>type, mapper) : type;
1033210329
}
10333-
if (type.flags & TypeFlags.Conditional) {
10334-
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>type).mapper, mapper));
10330+
if (objectFlags & ObjectFlags.Mapped) {
10331+
return getAnonymousTypeInstantiation(<AnonymousType>type, mapper);
1033510332
}
10336-
if (type.flags & TypeFlags.Substitution) {
10337-
return instantiateType((<SubstitutionType>type).typeVariable, mapper);
10333+
if (objectFlags & ObjectFlags.Reference) {
10334+
const typeArguments = (<TypeReference>type).typeArguments;
10335+
const newTypeArguments = instantiateTypes(typeArguments, mapper);
10336+
return newTypeArguments !== typeArguments ? createTypeReference((<TypeReference>type).target, newTypeArguments) : type;
1033810337
}
10338+
return type;
10339+
}
10340+
if (flags & TypeFlags.Union && !(flags & TypeFlags.Primitive)) {
10341+
const types = (<UnionType>type).types;
10342+
const newTypes = instantiateTypes(types, mapper);
10343+
return newTypes !== types ? getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type;
10344+
}
10345+
if (flags & TypeFlags.Intersection) {
10346+
const types = (<IntersectionType>type).types;
10347+
const newTypes = instantiateTypes(types, mapper);
10348+
return newTypes !== types ? getIntersectionType(newTypes, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type;
10349+
}
10350+
if (flags & TypeFlags.Index) {
10351+
return getIndexType(instantiateType((<IndexType>type).type, mapper));
10352+
}
10353+
if (flags & TypeFlags.IndexedAccess) {
10354+
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
10355+
}
10356+
if (flags & TypeFlags.Conditional) {
10357+
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>type).mapper, mapper));
10358+
}
10359+
if (flags & TypeFlags.Substitution) {
10360+
return instantiateType((<SubstitutionType>type).typeVariable, mapper);
1033910361
}
1034010362
return type;
1034110363
}

src/compiler/diagnosticMessages.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,10 +1964,6 @@
19641964
"category": "Error",
19651965
"code": 2549
19661966
},
1967-
"Generic type instantiation is excessively deep and possibly infinite.": {
1968-
"category": "Error",
1969-
"code": 2550
1970-
},
19711967
"Property '{0}' does not exist on type '{1}'. Did you mean '{2}'?": {
19721968
"category": "Error",
19731969
"code": 2551
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' cannot be used to index type 'B[Exclude<keyof B, K>]'.
2+
tests/cases/compiler/infiniteConstraints.ts(31,42): error TS2345: Argument of type '{ main: Record<"val", "dup">; alternate: Record<"val", "dup">; }' is not assignable to parameter of type '{ main: never; alternate: never; }'.
3+
Types of property 'main' are incompatible.
4+
Type 'Record<"val", "dup">' is not assignable to type 'never'.
5+
tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.
6+
7+
8+
==== tests/cases/compiler/infiniteConstraints.ts (3 errors) ====
9+
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint
10+
11+
type T1<B extends { [K in keyof B]: Extract<B[Exclude<keyof B, K>], { val: string }>["val"] }> = B;
12+
type T2<B extends { [K in keyof B]: B[Exclude<keyof B, K>]["val"] }> = B;
13+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
14+
!!! error TS2536: Type '"val"' cannot be used to index type 'B[Exclude<keyof B, K>]'.
15+
16+
// Repros from #22950
17+
18+
type AProp<T extends { a: string }> = T
19+
20+
declare function myBug<
21+
T extends { [K in keyof T]: T[K] extends AProp<infer U> ? U : never }
22+
>(arg: T): T
23+
24+
const out = myBug({obj1: {a: "test"}})
25+
26+
type Value<V extends string = string> = Record<"val", V>;
27+
declare function value<V extends string>(val: V): Value<V>;
28+
29+
declare function ensureNoDuplicates<
30+
T extends {
31+
[K in keyof T]: Extract<T[K], Value>["val"] extends Extract<T[Exclude<keyof T, K>], Value>["val"]
32+
? never
33+
: any
34+
}
35+
>(vals: T): void;
36+
37+
const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2")});
38+
39+
const shouldBeNoError = ensureNoDuplicates({main: value("test")});
40+
41+
const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("dup")});
42+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43+
!!! error TS2345: Argument of type '{ main: Record<"val", "dup">; alternate: Record<"val", "dup">; }' is not assignable to parameter of type '{ main: never; alternate: never; }'.
44+
!!! error TS2345: Types of property 'main' are incompatible.
45+
!!! error TS2345: Type 'Record<"val", "dup">' is not assignable to type 'never'.
46+
47+
// Repro from #26448
48+
49+
type Cond<T> = T extends number ? number : never;
50+
declare function function1<T extends {[K in keyof T]: Cond<T[K]>}>(): T[keyof T]["foo"];
51+
~~~~~~~~~~~~~~~~~
52+
!!! error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.
53+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//// [infiniteConstraints.ts]
2+
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint
3+
4+
type T1<B extends { [K in keyof B]: Extract<B[Exclude<keyof B, K>], { val: string }>["val"] }> = B;
5+
type T2<B extends { [K in keyof B]: B[Exclude<keyof B, K>]["val"] }> = B;
6+
7+
// Repros from #22950
8+
9+
type AProp<T extends { a: string }> = T
10+
11+
declare function myBug<
12+
T extends { [K in keyof T]: T[K] extends AProp<infer U> ? U : never }
13+
>(arg: T): T
14+
15+
const out = myBug({obj1: {a: "test"}})
16+
17+
type Value<V extends string = string> = Record<"val", V>;
18+
declare function value<V extends string>(val: V): Value<V>;
19+
20+
declare function ensureNoDuplicates<
21+
T extends {
22+
[K in keyof T]: Extract<T[K], Value>["val"] extends Extract<T[Exclude<keyof T, K>], Value>["val"]
23+
? never
24+
: any
25+
}
26+
>(vals: T): void;
27+
28+
const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2")});
29+
30+
const shouldBeNoError = ensureNoDuplicates({main: value("test")});
31+
32+
const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("dup")});
33+
34+
// Repro from #26448
35+
36+
type Cond<T> = T extends number ? number : never;
37+
declare function function1<T extends {[K in keyof T]: Cond<T[K]>}>(): T[keyof T]["foo"];
38+
39+
40+
//// [infiniteConstraints.js]
41+
"use strict";
42+
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint
43+
var out = myBug({ obj1: { a: "test" } });
44+
var noError = ensureNoDuplicates({ main: value("test"), alternate: value("test2") });
45+
var shouldBeNoError = ensureNoDuplicates({ main: value("test") });
46+
var shouldBeError = ensureNoDuplicates({ main: value("dup"), alternate: value("dup") });

0 commit comments

Comments
 (0)