From 44ada08e0204ed747e8bbab4c565c3588fb0119e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 20 Aug 2018 12:41:31 -0700 Subject: [PATCH 1/7] Guard against infinite type instantiations and constraints --- src/compiler/checker.ts | 57 ++++++++++++++++++---------- src/compiler/diagnosticMessages.json | 4 -- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9bcd6708b22..d82844f55fe 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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; @@ -5295,22 +5296,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; } @@ -7003,6 +6996,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)); @@ -7011,8 +7005,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; @@ -10282,12 +10286,25 @@ namespace ts { return getConditionalType(root, mapper); } + function instantiateWithDepthCheck(type: Type, mapper: TypeMapper, instantiator: (type: Type, mapper: TypeMapper) => Type): Type { + if (instantiationDepth < 50) { + instantiationDepth++; + const result = instantiator(type, mapper); + instantiationDepth--; + return result; + } + // 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; + } + 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(type); + return mapper(type); } if (type.flags & TypeFlags.Object) { if ((type).objectFlags & ObjectFlags.Anonymous) { @@ -10295,10 +10312,10 @@ namespace ts { // 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(type, mapper) : type; + instantiateWithDepthCheck(type, mapper, getAnonymousTypeInstantiation) : type; } if ((type).objectFlags & ObjectFlags.Mapped) { - return getAnonymousTypeInstantiation(type, mapper); + return instantiateWithDepthCheck(type, mapper, getAnonymousTypeInstantiation); } if ((type).objectFlags & ObjectFlags.Reference) { const typeArguments = (type).typeArguments; @@ -10323,7 +10340,7 @@ namespace ts { return getIndexedAccessType(instantiateType((type).objectType, mapper), instantiateType((type).indexType, mapper)); } if (type.flags & TypeFlags.Conditional) { - return getConditionalTypeInstantiation(type, combineTypeMappers((type).mapper, mapper)); + return instantiateWithDepthCheck(type, combineTypeMappers((type).mapper, mapper), getConditionalTypeInstantiation); } if (type.flags & TypeFlags.Substitution) { return instantiateType((type).typeVariable, mapper); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 6000c48de4d..32a07c0818d 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -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 From 6262ac8b3d5c3b50b26b4c88be545621de4a574c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 20 Aug 2018 12:41:48 -0700 Subject: [PATCH 2/7] Add tests --- tests/cases/compiler/infiniteConstraints.ts | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/cases/compiler/infiniteConstraints.ts diff --git a/tests/cases/compiler/infiniteConstraints.ts b/tests/cases/compiler/infiniteConstraints.ts new file mode 100644 index 00000000000..70d3879da6b --- /dev/null +++ b/tests/cases/compiler/infiniteConstraints.ts @@ -0,0 +1,33 @@ +// @strict: true + +// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint + +type T1], { val: string }>["val"] }> = B; +type T2]["val"] }> = B; + +// Repros from #22950 + +type AProp = T + +declare function myBug< + T extends { [K in keyof T]: T[K] extends AProp ? U : never } +>(arg: T): T + +const out = myBug({obj1: {a: "test"}}) + +type Value = Record<"val", V>; +declare function value(val: V): Value; + +declare function ensureNoDuplicates< + T extends { + [K in keyof T]: Extract["val"] extends Extract], 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")}); From a15fecdc52a9bbe16635ab222ec860d447f0d172 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 20 Aug 2018 12:41:56 -0700 Subject: [PATCH 3/7] Accept new baselines --- .../reference/infiniteConstraints.errors.txt | 45 +++++++ .../reference/infiniteConstraints.js | 41 ++++++ .../reference/infiniteConstraints.symbols | 122 ++++++++++++++++++ .../reference/infiniteConstraints.types | 89 +++++++++++++ 4 files changed, 297 insertions(+) create mode 100644 tests/baselines/reference/infiniteConstraints.errors.txt create mode 100644 tests/baselines/reference/infiniteConstraints.js create mode 100644 tests/baselines/reference/infiniteConstraints.symbols create mode 100644 tests/baselines/reference/infiniteConstraints.types diff --git a/tests/baselines/reference/infiniteConstraints.errors.txt b/tests/baselines/reference/infiniteConstraints.errors.txt new file mode 100644 index 00000000000..108c3693783 --- /dev/null +++ b/tests/baselines/reference/infiniteConstraints.errors.txt @@ -0,0 +1,45 @@ +tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' cannot be used to index type 'B[Exclude]'. +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 (2 errors) ==== + // Both of the following types trigger the recursion limiter in getImmediateBaseConstraint + + type T1], { val: string }>["val"] }> = B; + type T2]["val"] }> = B; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2536: Type '"val"' cannot be used to index type 'B[Exclude]'. + + // Repros from #22950 + + type AProp = T + + declare function myBug< + T extends { [K in keyof T]: T[K] extends AProp ? U : never } + >(arg: T): T + + const out = myBug({obj1: {a: "test"}}) + + type Value = Record<"val", V>; + declare function value(val: V): Value; + + declare function ensureNoDuplicates< + T extends { + [K in keyof T]: Extract["val"] extends Extract], 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'. + \ No newline at end of file diff --git a/tests/baselines/reference/infiniteConstraints.js b/tests/baselines/reference/infiniteConstraints.js new file mode 100644 index 00000000000..0ad3b676f14 --- /dev/null +++ b/tests/baselines/reference/infiniteConstraints.js @@ -0,0 +1,41 @@ +//// [infiniteConstraints.ts] +// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint + +type T1], { val: string }>["val"] }> = B; +type T2]["val"] }> = B; + +// Repros from #22950 + +type AProp = T + +declare function myBug< + T extends { [K in keyof T]: T[K] extends AProp ? U : never } +>(arg: T): T + +const out = myBug({obj1: {a: "test"}}) + +type Value = Record<"val", V>; +declare function value(val: V): Value; + +declare function ensureNoDuplicates< + T extends { + [K in keyof T]: Extract["val"] extends Extract], 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")}); + + +//// [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") }); diff --git a/tests/baselines/reference/infiniteConstraints.symbols b/tests/baselines/reference/infiniteConstraints.symbols new file mode 100644 index 00000000000..0f0d44c9f53 --- /dev/null +++ b/tests/baselines/reference/infiniteConstraints.symbols @@ -0,0 +1,122 @@ +=== tests/cases/compiler/infiniteConstraints.ts === +// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint + +type T1], { val: string }>["val"] }> = B; +>T1 : Symbol(T1, Decl(infiniteConstraints.ts, 0, 0)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 2, 8)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 2, 21)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 2, 8)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 2, 8)) +>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 2, 8)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 2, 21)) +>val : Symbol(val, Decl(infiniteConstraints.ts, 2, 69)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 2, 8)) + +type T2]["val"] }> = B; +>T2 : Symbol(T2, Decl(infiniteConstraints.ts, 2, 99)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 3, 8)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 3, 21)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 3, 8)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 3, 8)) +>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 3, 8)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 3, 21)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 3, 8)) + +// Repros from #22950 + +type AProp = T +>AProp : Symbol(AProp, Decl(infiniteConstraints.ts, 3, 73)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 7, 11)) +>a : Symbol(a, Decl(infiniteConstraints.ts, 7, 22)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 7, 11)) + +declare function myBug< +>myBug : Symbol(myBug, Decl(infiniteConstraints.ts, 7, 39)) + + T extends { [K in keyof T]: T[K] extends AProp ? U : never } +>T : Symbol(T, Decl(infiniteConstraints.ts, 9, 23)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 10, 15)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 9, 23)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 9, 23)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 10, 15)) +>AProp : Symbol(AProp, Decl(infiniteConstraints.ts, 3, 73)) +>U : Symbol(U, Decl(infiniteConstraints.ts, 10, 54)) +>U : Symbol(U, Decl(infiniteConstraints.ts, 10, 54)) + +>(arg: T): T +>arg : Symbol(arg, Decl(infiniteConstraints.ts, 11, 2)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 9, 23)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 9, 23)) + +const out = myBug({obj1: {a: "test"}}) +>out : Symbol(out, Decl(infiniteConstraints.ts, 13, 5)) +>myBug : Symbol(myBug, Decl(infiniteConstraints.ts, 7, 39)) +>obj1 : Symbol(obj1, Decl(infiniteConstraints.ts, 13, 19)) +>a : Symbol(a, Decl(infiniteConstraints.ts, 13, 26)) + +type Value = Record<"val", V>; +>Value : Symbol(Value, Decl(infiniteConstraints.ts, 13, 38)) +>V : Symbol(V, Decl(infiniteConstraints.ts, 15, 11)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>V : Symbol(V, Decl(infiniteConstraints.ts, 15, 11)) + +declare function value(val: V): Value; +>value : Symbol(value, Decl(infiniteConstraints.ts, 15, 57)) +>V : Symbol(V, Decl(infiniteConstraints.ts, 16, 23)) +>val : Symbol(val, Decl(infiniteConstraints.ts, 16, 41)) +>V : Symbol(V, Decl(infiniteConstraints.ts, 16, 23)) +>Value : Symbol(Value, Decl(infiniteConstraints.ts, 13, 38)) +>V : Symbol(V, Decl(infiniteConstraints.ts, 16, 23)) + +declare function ensureNoDuplicates< +>ensureNoDuplicates : Symbol(ensureNoDuplicates, Decl(infiniteConstraints.ts, 16, 59)) + + T extends { +>T : Symbol(T, Decl(infiniteConstraints.ts, 18, 36)) + + [K in keyof T]: Extract["val"] extends Extract], Value>["val"] +>K : Symbol(K, Decl(infiniteConstraints.ts, 20, 5)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 18, 36)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 18, 36)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 20, 5)) +>Value : Symbol(Value, Decl(infiniteConstraints.ts, 13, 38)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 18, 36)) +>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 18, 36)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 20, 5)) +>Value : Symbol(Value, Decl(infiniteConstraints.ts, 13, 38)) + + ? never + : any + } +>(vals: T): void; +>vals : Symbol(vals, Decl(infiniteConstraints.ts, 24, 2)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 18, 36)) + +const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2")}); +>noError : Symbol(noError, Decl(infiniteConstraints.ts, 26, 5)) +>ensureNoDuplicates : Symbol(ensureNoDuplicates, Decl(infiniteConstraints.ts, 16, 59)) +>main : Symbol(main, Decl(infiniteConstraints.ts, 26, 36)) +>value : Symbol(value, Decl(infiniteConstraints.ts, 15, 57)) +>alternate : Symbol(alternate, Decl(infiniteConstraints.ts, 26, 56)) +>value : Symbol(value, Decl(infiniteConstraints.ts, 15, 57)) + +const shouldBeNoError = ensureNoDuplicates({main: value("test")}); +>shouldBeNoError : Symbol(shouldBeNoError, Decl(infiniteConstraints.ts, 28, 5)) +>ensureNoDuplicates : Symbol(ensureNoDuplicates, Decl(infiniteConstraints.ts, 16, 59)) +>main : Symbol(main, Decl(infiniteConstraints.ts, 28, 44)) +>value : Symbol(value, Decl(infiniteConstraints.ts, 15, 57)) + +const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("dup")}); +>shouldBeError : Symbol(shouldBeError, Decl(infiniteConstraints.ts, 30, 5)) +>ensureNoDuplicates : Symbol(ensureNoDuplicates, Decl(infiniteConstraints.ts, 16, 59)) +>main : Symbol(main, Decl(infiniteConstraints.ts, 30, 42)) +>value : Symbol(value, Decl(infiniteConstraints.ts, 15, 57)) +>alternate : Symbol(alternate, Decl(infiniteConstraints.ts, 30, 61)) +>value : Symbol(value, Decl(infiniteConstraints.ts, 15, 57)) + diff --git a/tests/baselines/reference/infiniteConstraints.types b/tests/baselines/reference/infiniteConstraints.types new file mode 100644 index 00000000000..78fbc9becaa --- /dev/null +++ b/tests/baselines/reference/infiniteConstraints.types @@ -0,0 +1,89 @@ +=== tests/cases/compiler/infiniteConstraints.ts === +// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint + +type T1], { val: string }>["val"] }> = B; +>T1 : B +>val : string + +type T2]["val"] }> = B; +>T2 : B + +// Repros from #22950 + +type AProp = T +>AProp : T +>a : string + +declare function myBug< +>myBug : (arg: T) => T + + T extends { [K in keyof T]: T[K] extends AProp ? U : never } +>(arg: T): T +>arg : T + +const out = myBug({obj1: {a: "test"}}) +>out : { obj1: { a: string; }; } +>myBug({obj1: {a: "test"}}) : { obj1: { a: string; }; } +>myBug : (arg: T) => T +>{obj1: {a: "test"}} : { obj1: { a: string; }; } +>obj1 : { a: string; } +>{a: "test"} : { a: string; } +>a : string +>"test" : "test" + +type Value = Record<"val", V>; +>Value : Record<"val", V> + +declare function value(val: V): Value; +>value : (val: V) => Record<"val", V> +>val : V + +declare function ensureNoDuplicates< +>ensureNoDuplicates : >["val"] extends Extract], Record<"val", string>>["val"] ? never : any; }>(vals: T) => void + + T extends { + [K in keyof T]: Extract["val"] extends Extract], Value>["val"] + ? never + : any + } +>(vals: T): void; +>vals : T + +const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2")}); +>noError : void +>ensureNoDuplicates({main: value("test"), alternate: value("test2")}) : void +>ensureNoDuplicates : >["val"] extends Extract], Record<"val", string>>["val"] ? never : any; }>(vals: T) => void +>{main: value("test"), alternate: value("test2")} : { main: Record<"val", "test">; alternate: Record<"val", "test2">; } +>main : Record<"val", "test"> +>value("test") : Record<"val", "test"> +>value : (val: V) => Record<"val", V> +>"test" : "test" +>alternate : Record<"val", "test2"> +>value("test2") : Record<"val", "test2"> +>value : (val: V) => Record<"val", V> +>"test2" : "test2" + +const shouldBeNoError = ensureNoDuplicates({main: value("test")}); +>shouldBeNoError : void +>ensureNoDuplicates({main: value("test")}) : void +>ensureNoDuplicates : >["val"] extends Extract], Record<"val", string>>["val"] ? never : any; }>(vals: T) => void +>{main: value("test")} : { main: Record<"val", "test">; } +>main : Record<"val", "test"> +>value("test") : Record<"val", "test"> +>value : (val: V) => Record<"val", V> +>"test" : "test" + +const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("dup")}); +>shouldBeError : any +>ensureNoDuplicates({main: value("dup"), alternate: value("dup")}) : any +>ensureNoDuplicates : >["val"] extends Extract], Record<"val", string>>["val"] ? never : any; }>(vals: T) => void +>{main: value("dup"), alternate: value("dup")} : { main: Record<"val", "dup">; alternate: Record<"val", "dup">; } +>main : Record<"val", "dup"> +>value("dup") : Record<"val", "dup"> +>value : (val: V) => Record<"val", V> +>"dup" : "dup" +>alternate : Record<"val", "dup"> +>value("dup") : Record<"val", "dup"> +>value : (val: V) => Record<"val", V> +>"dup" : "dup" + From 7c2644a6762efd2b1937bd406c346d3c67d75c52 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 21 Aug 2018 06:30:47 -0700 Subject: [PATCH 4/7] Perform depth check for all type instantiations --- src/compiler/checker.ts | 110 +++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 53 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b8dd117d6fd..5d2b0187eb0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10294,65 +10294,69 @@ namespace ts { return getConditionalType(root, mapper); } - function instantiateWithDepthCheck(type: Type, mapper: TypeMapper, instantiator: (type: Type, mapper: TypeMapper) => Type): Type { - if (instantiationDepth < 50) { - instantiationDepth++; - const result = instantiator(type, mapper); - instantiationDepth--; - return result; - } - // 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; - } - 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(type); + 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 = (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(type, mapper) : type; } - if (type.flags & TypeFlags.Object) { - if ((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 ? - instantiateWithDepthCheck(type, mapper, getAnonymousTypeInstantiation) : type; - } - if ((type).objectFlags & ObjectFlags.Mapped) { - return instantiateWithDepthCheck(type, mapper, getAnonymousTypeInstantiation); - } - if ((type).objectFlags & ObjectFlags.Reference) { - const typeArguments = (type).typeArguments; - const newTypeArguments = instantiateTypes(typeArguments, mapper); - return newTypeArguments !== typeArguments ? createTypeReference((type).target, newTypeArguments) : type; - } + if (objectFlags & ObjectFlags.Mapped) { + return getAnonymousTypeInstantiation(type, mapper); } - if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) { - const types = (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 = (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((type).type, mapper)); - } - if (type.flags & TypeFlags.IndexedAccess) { - return getIndexedAccessType(instantiateType((type).objectType, mapper), instantiateType((type).indexType, mapper)); - } - if (type.flags & TypeFlags.Conditional) { - return instantiateWithDepthCheck(type, combineTypeMappers((type).mapper, mapper), getConditionalTypeInstantiation); - } - if (type.flags & TypeFlags.Substitution) { - return instantiateType((type).typeVariable, mapper); + if (objectFlags & ObjectFlags.Reference) { + const typeArguments = (type).typeArguments; + const newTypeArguments = instantiateTypes(typeArguments, mapper); + return newTypeArguments !== typeArguments ? createTypeReference((type).target, newTypeArguments) : type; } + return type; + } + if (flags & TypeFlags.Union && !(flags & TypeFlags.Primitive)) { + const types = (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 = (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((type).type, mapper)); + } + if (flags & TypeFlags.IndexedAccess) { + return getIndexedAccessType(instantiateType((type).objectType, mapper), instantiateType((type).indexType, mapper)); + } + if (flags & TypeFlags.Conditional) { + return getConditionalTypeInstantiation(type, combineTypeMappers((type).mapper, mapper)); + } + if (flags & TypeFlags.Substitution) { + return instantiateType((type).typeVariable, mapper); } return type; } From 7740822d02d8072f6360090ab10a953775fbe1a3 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 21 Aug 2018 15:45:29 -0700 Subject: [PATCH 5/7] Simplify or get constraint in distributive conditional type, but not both --- src/compiler/checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5d2b0187eb0..8f043d51cda 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6921,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)); From db9c202b64d8a26bd5aee42d24dd39693aab2a43 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 21 Aug 2018 16:17:14 -0700 Subject: [PATCH 6/7] Add regression test --- tests/cases/compiler/infiniteConstraints.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/cases/compiler/infiniteConstraints.ts b/tests/cases/compiler/infiniteConstraints.ts index 70d3879da6b..e914b738efc 100644 --- a/tests/cases/compiler/infiniteConstraints.ts +++ b/tests/cases/compiler/infiniteConstraints.ts @@ -31,3 +31,8 @@ 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 extends number ? number : never; +declare function function1}>(): T[keyof T]["foo"]; From 2f85af83a7b689468fc8a0fc4ac66d6a7df4323a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 21 Aug 2018 16:17:21 -0700 Subject: [PATCH 7/7] Accept new baselines --- .../reference/infiniteConstraints.errors.txt | 10 +++++++++- .../baselines/reference/infiniteConstraints.js | 5 +++++ .../reference/infiniteConstraints.symbols | 18 ++++++++++++++++++ .../reference/infiniteConstraints.types | 8 ++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/infiniteConstraints.errors.txt b/tests/baselines/reference/infiniteConstraints.errors.txt index 108c3693783..387b0daac68 100644 --- a/tests/baselines/reference/infiniteConstraints.errors.txt +++ b/tests/baselines/reference/infiniteConstraints.errors.txt @@ -2,9 +2,10 @@ tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' ca 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 (2 errors) ==== +==== tests/cases/compiler/infiniteConstraints.ts (3 errors) ==== // Both of the following types trigger the recursion limiter in getImmediateBaseConstraint type T1], { val: string }>["val"] }> = B; @@ -42,4 +43,11 @@ tests/cases/compiler/infiniteConstraints.ts(31,42): error TS2345: Argument of ty !!! 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 extends number ? number : never; + declare function function1}>(): T[keyof T]["foo"]; + ~~~~~~~~~~~~~~~~~ +!!! error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'. \ No newline at end of file diff --git a/tests/baselines/reference/infiniteConstraints.js b/tests/baselines/reference/infiniteConstraints.js index 0ad3b676f14..03a4d70e00c 100644 --- a/tests/baselines/reference/infiniteConstraints.js +++ b/tests/baselines/reference/infiniteConstraints.js @@ -30,6 +30,11 @@ 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 extends number ? number : never; +declare function function1}>(): T[keyof T]["foo"]; //// [infiniteConstraints.js] diff --git a/tests/baselines/reference/infiniteConstraints.symbols b/tests/baselines/reference/infiniteConstraints.symbols index 0f0d44c9f53..6306203a7e0 100644 --- a/tests/baselines/reference/infiniteConstraints.symbols +++ b/tests/baselines/reference/infiniteConstraints.symbols @@ -120,3 +120,21 @@ const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value(" >alternate : Symbol(alternate, Decl(infiniteConstraints.ts, 30, 61)) >value : Symbol(value, Decl(infiniteConstraints.ts, 15, 57)) +// Repro from #26448 + +type Cond = T extends number ? number : never; +>Cond : Symbol(Cond, Decl(infiniteConstraints.ts, 30, 88)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 34, 10)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 34, 10)) + +declare function function1}>(): T[keyof T]["foo"]; +>function1 : Symbol(function1, Decl(infiniteConstraints.ts, 34, 49)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 35, 27)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 35, 39)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 35, 27)) +>Cond : Symbol(Cond, Decl(infiniteConstraints.ts, 30, 88)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 35, 27)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 35, 39)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 35, 27)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 35, 27)) + diff --git a/tests/baselines/reference/infiniteConstraints.types b/tests/baselines/reference/infiniteConstraints.types index 78fbc9becaa..e23e138c820 100644 --- a/tests/baselines/reference/infiniteConstraints.types +++ b/tests/baselines/reference/infiniteConstraints.types @@ -87,3 +87,11 @@ const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value(" >value : (val: V) => Record<"val", V> >"dup" : "dup" +// Repro from #26448 + +type Cond = T extends number ? number : never; +>Cond : Cond + +declare function function1}>(): T[keyof T]["foo"]; +>function1 : ; }>() => T[keyof T]["foo"] +