Skip to content

Fix infinite constraints #26558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 79 additions & 57 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ namespace ts {
let typeCount = 0;
let symbolCount = 0;
let enumCount = 0;
let symbolInstantiationDepth = 0;
let instantiationDepth = 0;
let constraintDepth = 0;

const emptySymbols = createSymbolTable();
const identityMapper: (type: Type) => Type = identity;
Expand Down Expand Up @@ -5303,22 +5304,14 @@ namespace ts {
function getTypeOfInstantiatedSymbol(symbol: Symbol): Type {
const links = getSymbolLinks(symbol);
if (!links.type) {
if (symbolInstantiationDepth === 100) {
error(symbol.valueDeclaration, Diagnostics.Generic_type_instantiation_is_excessively_deep_and_possibly_infinite);
links.type = errorType;
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
return links.type = errorType;
}
else {
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
return links.type = errorType;
}
symbolInstantiationDepth++;
let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper!);
symbolInstantiationDepth--;
if (!popTypeResolution()) {
type = reportCircularityError(symbol);
}
links.type = type;
let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper!);
if (!popTypeResolution()) {
type = reportCircularityError(symbol);
}
links.type = type;
}
return links.type;
}
Expand Down Expand Up @@ -6928,7 +6921,8 @@ namespace ts {
// over the conditional type and possibly reduced. For example, 'T extends undefined ? never : T'
// removes 'undefined' from T.
if (type.root.isDistributive) {
const constraint = getConstraintOfType(getSimplifiedType(type.checkType));
const simplified = getSimplifiedType(type.checkType);
const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified;
if (constraint) {
const mapper = makeUnaryTypeMapper(type.root.checkType, constraint);
const instantiated = getConditionalTypeInstantiation(type, combineTypeMappers(mapper, type.mapper));
Expand Down Expand Up @@ -7011,6 +7005,7 @@ namespace ts {
* circularly references the type variable.
*/
function getResolvedBaseConstraint(type: InstantiableType | UnionOrIntersectionType): Type {
let nonTerminating = false;
return type.resolvedBaseConstraint ||
(type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type));

Expand All @@ -7019,8 +7014,18 @@ namespace ts {
if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) {
return circularConstraintType;
}
if (constraintDepth === 50) {
// We have reached 50 recursive invocations of getImmediateBaseConstraint and there is a
// very high likelyhood we're dealing with an infinite generic type that perpetually generates
// new type identities as we descend into it. We stop the recursion here and mark this type
// and the outer types as having circular constraints.
nonTerminating = true;
return t.immediateBaseConstraint = noConstraintType;
}
constraintDepth++;
let result = computeBaseConstraint(getSimplifiedType(t));
if (!popTypeResolution()) {
constraintDepth--;
if (!popTypeResolution() || nonTerminating) {
result = circularConstraintType;
}
t.immediateBaseConstraint = result || noConstraintType;
Expand Down Expand Up @@ -10293,49 +10298,66 @@ namespace ts {
function instantiateType(type: Type, mapper: TypeMapper | undefined): Type;
function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined;
function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined {
if (type && mapper && mapper !== identityMapper) {
if (type.flags & TypeFlags.TypeParameter) {
return mapper(<TypeParameter>type);
}
if (type.flags & TypeFlags.Object) {
if ((<ObjectType>type).objectFlags & ObjectFlags.Anonymous) {
// If the anonymous type originates in a declaration of a function, method, class, or
// interface, in an object type literal, or in an object literal expression, we may need
// to instantiate the type because it might reference a type parameter.
return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ?
getAnonymousTypeInstantiation(<AnonymousType>type, mapper) : type;
}
if ((<ObjectType>type).objectFlags & ObjectFlags.Mapped) {
return getAnonymousTypeInstantiation(<MappedType>type, mapper);
}
if ((<ObjectType>type).objectFlags & ObjectFlags.Reference) {
const typeArguments = (<TypeReference>type).typeArguments;
const newTypeArguments = instantiateTypes(typeArguments, mapper);
return newTypeArguments !== typeArguments ? createTypeReference((<TypeReference>type).target, newTypeArguments) : type;
}
}
if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) {
const types = (<UnionType>type).types;
const newTypes = instantiateTypes(types, mapper);
return newTypes !== types ? getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type;
}
if (type.flags & TypeFlags.Intersection) {
const types = (<IntersectionType>type).types;
const newTypes = instantiateTypes(types, mapper);
return newTypes !== types ? getIntersectionType(newTypes, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type;
}
if (type.flags & TypeFlags.Index) {
return getIndexType(instantiateType((<IndexType>type).type, mapper));
}
if (type.flags & TypeFlags.IndexedAccess) {
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
if (!type || !mapper || mapper === identityMapper) {
return type;
}
if (instantiationDepth === 50) {
// We have reached 50 recursive type instantiations and there is a very high likelyhood we're dealing
// with a combination of infinite generic types that perpetually generate new type identities. We stop
// the recursion here by yielding the error type.
return errorType;
}
instantiationDepth++;
const result = instantiateTypeWorker(type, mapper);
instantiationDepth--;
return result;
}

function instantiateTypeWorker(type: Type, mapper: TypeMapper): Type {
const flags = type.flags;
if (flags & TypeFlags.TypeParameter) {
return mapper(type);
}
if (flags & TypeFlags.Object) {
const objectFlags = (<ObjectType>type).objectFlags;
if (objectFlags & ObjectFlags.Anonymous) {
// If the anonymous type originates in a declaration of a function, method, class, or
// interface, in an object type literal, or in an object literal expression, we may need
// to instantiate the type because it might reference a type parameter.
return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ?
getAnonymousTypeInstantiation(<AnonymousType>type, mapper) : type;
}
if (type.flags & TypeFlags.Conditional) {
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>type).mapper, mapper));
if (objectFlags & ObjectFlags.Mapped) {
return getAnonymousTypeInstantiation(<AnonymousType>type, mapper);
}
if (type.flags & TypeFlags.Substitution) {
return instantiateType((<SubstitutionType>type).typeVariable, mapper);
if (objectFlags & ObjectFlags.Reference) {
const typeArguments = (<TypeReference>type).typeArguments;
const newTypeArguments = instantiateTypes(typeArguments, mapper);
return newTypeArguments !== typeArguments ? createTypeReference((<TypeReference>type).target, newTypeArguments) : type;
}
return type;
}
if (flags & TypeFlags.Union && !(flags & TypeFlags.Primitive)) {
const types = (<UnionType>type).types;
const newTypes = instantiateTypes(types, mapper);
return newTypes !== types ? getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type;
}
if (flags & TypeFlags.Intersection) {
const types = (<IntersectionType>type).types;
const newTypes = instantiateTypes(types, mapper);
return newTypes !== types ? getIntersectionType(newTypes, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type;
}
if (flags & TypeFlags.Index) {
return getIndexType(instantiateType((<IndexType>type).type, mapper));
}
if (flags & TypeFlags.IndexedAccess) {
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
}
if (flags & TypeFlags.Conditional) {
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>type).mapper, mapper));
}
if (flags & TypeFlags.Substitution) {
return instantiateType((<SubstitutionType>type).typeVariable, mapper);
}
return type;
}
Expand Down
4 changes: 0 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1964,10 +1964,6 @@
"category": "Error",
"code": 2549
},
"Generic type instantiation is excessively deep and possibly infinite.": {
"category": "Error",
"code": 2550
},
"Property '{0}' does not exist on type '{1}'. Did you mean '{2}'?": {
"category": "Error",
"code": 2551
Expand Down
53 changes: 53 additions & 0 deletions tests/baselines/reference/infiniteConstraints.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' cannot be used to index type 'B[Exclude<keyof B, K>]'.
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; }'.
Types of property 'main' are incompatible.
Type 'Record<"val", "dup">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.


==== tests/cases/compiler/infiniteConstraints.ts (3 errors) ====
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint

type T1<B extends { [K in keyof B]: Extract<B[Exclude<keyof B, K>], { val: string }>["val"] }> = B;
type T2<B extends { [K in keyof B]: B[Exclude<keyof B, K>]["val"] }> = B;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2536: Type '"val"' cannot be used to index type 'B[Exclude<keyof B, K>]'.

// Repros from #22950

type AProp<T extends { a: string }> = T

declare function myBug<
T extends { [K in keyof T]: T[K] extends AProp<infer U> ? U : never }
>(arg: T): T

const out = myBug({obj1: {a: "test"}})

type Value<V extends string = string> = Record<"val", V>;
declare function value<V extends string>(val: V): Value<V>;

declare function ensureNoDuplicates<
T extends {
[K in keyof T]: Extract<T[K], Value>["val"] extends Extract<T[Exclude<keyof T, K>], Value>["val"]
? never
: any
}
>(vals: T): void;

const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2")});

const shouldBeNoError = ensureNoDuplicates({main: value("test")});

const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("dup")});
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2345: Argument of type '{ main: Record<"val", "dup">; alternate: Record<"val", "dup">; }' is not assignable to parameter of type '{ main: never; alternate: never; }'.
!!! error TS2345: Types of property 'main' are incompatible.
!!! error TS2345: Type 'Record<"val", "dup">' is not assignable to type 'never'.

// Repro from #26448

type Cond<T> = T extends number ? number : never;
declare function function1<T extends {[K in keyof T]: Cond<T[K]>}>(): T[keyof T]["foo"];
~~~~~~~~~~~~~~~~~
!!! error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.

46 changes: 46 additions & 0 deletions tests/baselines/reference/infiniteConstraints.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//// [infiniteConstraints.ts]
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint

type T1<B extends { [K in keyof B]: Extract<B[Exclude<keyof B, K>], { val: string }>["val"] }> = B;
type T2<B extends { [K in keyof B]: B[Exclude<keyof B, K>]["val"] }> = B;

// Repros from #22950

type AProp<T extends { a: string }> = T

declare function myBug<
T extends { [K in keyof T]: T[K] extends AProp<infer U> ? U : never }
>(arg: T): T

const out = myBug({obj1: {a: "test"}})

type Value<V extends string = string> = Record<"val", V>;
declare function value<V extends string>(val: V): Value<V>;

declare function ensureNoDuplicates<
T extends {
[K in keyof T]: Extract<T[K], Value>["val"] extends Extract<T[Exclude<keyof T, K>], Value>["val"]
? never
: any
}
>(vals: T): void;

const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2")});

const shouldBeNoError = ensureNoDuplicates({main: value("test")});

const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("dup")});

// Repro from #26448

type Cond<T> = T extends number ? number : never;
declare function function1<T extends {[K in keyof T]: Cond<T[K]>}>(): T[keyof T]["foo"];


//// [infiniteConstraints.js]
"use strict";
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint
var out = myBug({ obj1: { a: "test" } });
var noError = ensureNoDuplicates({ main: value("test"), alternate: value("test2") });
var shouldBeNoError = ensureNoDuplicates({ main: value("test") });
var shouldBeError = ensureNoDuplicates({ main: value("dup"), alternate: value("dup") });
Loading