From b706a4c28af41c0c2ad622e70be17ce620971428 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 6 Oct 2020 15:44:04 -0700 Subject: [PATCH 1/5] Add isDeeplyNestedType logic to getResolvedBaseConstraint --- src/compiler/checker.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8d4bec287a9b7..f743627c4d9a8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10913,9 +10913,12 @@ namespace ts { * circularly references the type variable. */ function getResolvedBaseConstraint(type: InstantiableType | UnionOrIntersectionType): Type { + if (type.resolvedBaseConstraint) { + return type.resolvedBaseConstraint; + } let nonTerminating = false; - return type.resolvedBaseConstraint || - (type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type)); + let stack: Type[] = []; + return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type); function getImmediateBaseConstraint(t: Type): Type { if (!t.immediateBaseConstraint) { @@ -10932,9 +10935,14 @@ namespace ts { nonTerminating = true; return t.immediateBaseConstraint = noConstraintType; } - constraintDepth++; - let result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false)); - constraintDepth--; + let result; + if (!isDeeplyNestedType(t, stack, stack.length)) { + stack.push(t); + constraintDepth++; + result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false)); + constraintDepth--; + stack.pop(); + } if (!popTypeResolution()) { if (t.flags & TypeFlags.TypeParameter) { const errorNode = getConstraintDeclaration(t); From d7691e344c3ecd89aeea0b3ea59880c7bf021e5b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 6 Oct 2020 15:45:09 -0700 Subject: [PATCH 2/5] Accept new baselines --- tests/baselines/reference/infiniteConstraints.errors.txt | 5 +---- .../baselines/reference/recursiveConditionalTypes.errors.txt | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/baselines/reference/infiniteConstraints.errors.txt b/tests/baselines/reference/infiniteConstraints.errors.txt index 2548b00f04436..7e65945823896 100644 --- a/tests/baselines/reference/infiniteConstraints.errors.txt +++ b/tests/baselines/reference/infiniteConstraints.errors.txt @@ -2,10 +2,9 @@ tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' ca tests/cases/compiler/infiniteConstraints.ts(31,43): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'. tests/cases/compiler/infiniteConstraints.ts(31,63): error TS2322: 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(48,16): error TS2589: Type instantiation is excessively deep and possibly infinite. -==== tests/cases/compiler/infiniteConstraints.ts (5 errors) ==== +==== tests/cases/compiler/infiniteConstraints.ts (4 errors) ==== // Both of the following types trigger the recursion limiter in getImmediateBaseConstraint type T1], { val: string }>["val"] }> = B; @@ -64,6 +63,4 @@ tests/cases/compiler/infiniteConstraints.ts(48,16): error TS2589: Type instantia type Conv = { 0: [T]; 1: Prepend>>;}[U extends T ? 0 : 1]; - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2589: Type instantiation is excessively deep and possibly infinite. \ No newline at end of file diff --git a/tests/baselines/reference/recursiveConditionalTypes.errors.txt b/tests/baselines/reference/recursiveConditionalTypes.errors.txt index 907919a46bbd3..b9ae411978f2d 100644 --- a/tests/baselines/reference/recursiveConditionalTypes.errors.txt +++ b/tests/baselines/reference/recursiveConditionalTypes.errors.txt @@ -21,7 +21,6 @@ tests/cases/compiler/recursiveConditionalTypes.ts(50,5): error TS2322: Type 'Tup Type 'number extends N ? number[] : _TupleOf' is not assignable to type 'TupleOf'. Type 'number[] | _TupleOf' is not assignable to type 'TupleOf'. Type 'number[]' is not assignable to type 'TupleOf'. -tests/cases/compiler/recursiveConditionalTypes.ts(116,5): error TS2589: Type instantiation is excessively deep and possibly infinite. tests/cases/compiler/recursiveConditionalTypes.ts(116,9): error TS2345: Argument of type 'Grow2<[], T>' is not assignable to parameter of type 'Grow1<[], T>'. Type '[] | Grow2<[string], T>' is not assignable to type 'Grow1<[], T>'. Type '[]' is not assignable to type 'Grow1<[], T>'. @@ -32,7 +31,7 @@ tests/cases/compiler/recursiveConditionalTypes.ts(116,9): error TS2345: Argument Type 'string' is not assignable to type 'number'. -==== tests/cases/compiler/recursiveConditionalTypes.ts (10 errors) ==== +==== tests/cases/compiler/recursiveConditionalTypes.ts (9 errors) ==== // Awaiting promises type Awaited = @@ -180,8 +179,6 @@ tests/cases/compiler/recursiveConditionalTypes.ts(116,9): error TS2345: Argument function f21(x: Grow1<[], T>, y: Grow2<[], T>) { f21(y, x); // Error - ~~~~~~~~~ -!!! error TS2589: Type instantiation is excessively deep and possibly infinite. ~ !!! error TS2345: Argument of type 'Grow2<[], T>' is not assignable to parameter of type 'Grow1<[], T>'. !!! error TS2345: Type '[] | Grow2<[string], T>' is not assignable to type 'Grow1<[], T>'. From 81e78b81dca4a9d575fa239f8c5199d4c3b1600e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 6 Oct 2020 15:51:26 -0700 Subject: [PATCH 3/5] Add regression test --- .../types/literal/templateLiteralTypes1.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/cases/conformance/types/literal/templateLiteralTypes1.ts b/tests/cases/conformance/types/literal/templateLiteralTypes1.ts index a708a47b3a142..185e0bf402d33 100644 --- a/tests/cases/conformance/types/literal/templateLiteralTypes1.ts +++ b/tests/cases/conformance/types/literal/templateLiteralTypes1.ts @@ -215,3 +215,25 @@ type AA = [true, true] extends [IsNegative, IsNegative] ? 'Every thing is ok!' : ['strange', IsNegative, IsNegative]; type BB = AA<-2, -2>; + +// Repro from #40970 + +type PathKeys = + T extends readonly any[] ? Extract | SubKeys> : + T extends object ? Extract | SubKeys> : + never; + +type SubKeys = K extends keyof T ? `${K}.${PathKeys}` : never; + +declare function getProp2>(obj: T, path: P): PropType; + +const obj2 = { + name: 'John', + age: 42, + cars: [ + { make: 'Ford', age: 10 }, + { make: 'Trabant', age: 35 } + ] +} as const; + +let make = getProp2(obj2, 'cars.1.make'); // 'Trabant' From ee1f262cf2433ce4ade7fa696d1519af5ae25948 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 6 Oct 2020 15:51:31 -0700 Subject: [PATCH 4/5] Accept new baselines --- .../templateLiteralTypes1.errors.txt | 22 ++++++ .../reference/templateLiteralTypes1.js | 46 +++++++++++ .../reference/templateLiteralTypes1.symbols | 79 +++++++++++++++++++ .../reference/templateLiteralTypes1.types | 58 ++++++++++++++ 4 files changed, 205 insertions(+) diff --git a/tests/baselines/reference/templateLiteralTypes1.errors.txt b/tests/baselines/reference/templateLiteralTypes1.errors.txt index bce55c2b12250..eef69e2515666 100644 --- a/tests/baselines/reference/templateLiteralTypes1.errors.txt +++ b/tests/baselines/reference/templateLiteralTypes1.errors.txt @@ -241,4 +241,26 @@ tests/cases/conformance/types/literal/templateLiteralTypes1.ts(205,16): error TS [true, true] extends [IsNegative, IsNegative] ? 'Every thing is ok!' : ['strange', IsNegative, IsNegative]; type BB = AA<-2, -2>; + + // Repro from #40970 + + type PathKeys = + T extends readonly any[] ? Extract | SubKeys> : + T extends object ? Extract | SubKeys> : + never; + + type SubKeys = K extends keyof T ? `${K}.${PathKeys}` : never; + + declare function getProp2>(obj: T, path: P): PropType; + + const obj2 = { + name: 'John', + age: 42, + cars: [ + { make: 'Ford', age: 10 }, + { make: 'Trabant', age: 35 } + ] + } as const; + + let make = getProp2(obj2, 'cars.1.make'); // 'Trabant' \ No newline at end of file diff --git a/tests/baselines/reference/templateLiteralTypes1.js b/tests/baselines/reference/templateLiteralTypes1.js index 8266f6c50f0b3..5f93208af2fa2 100644 --- a/tests/baselines/reference/templateLiteralTypes1.js +++ b/tests/baselines/reference/templateLiteralTypes1.js @@ -213,6 +213,28 @@ type AA = [true, true] extends [IsNegative, IsNegative] ? 'Every thing is ok!' : ['strange', IsNegative, IsNegative]; type BB = AA<-2, -2>; + +// Repro from #40970 + +type PathKeys = + T extends readonly any[] ? Extract | SubKeys> : + T extends object ? Extract | SubKeys> : + never; + +type SubKeys = K extends keyof T ? `${K}.${PathKeys}` : never; + +declare function getProp2>(obj: T, path: P): PropType; + +const obj2 = { + name: 'John', + age: 42, + cars: [ + { make: 'Ford', age: 10 }, + { make: 'Trabant', age: 35 } + ] +} as const; + +let make = getProp2(obj2, 'cars.1.make'); // 'Trabant' //// [templateLiteralTypes1.js] @@ -243,6 +265,15 @@ getPropValue(obj, 'a.b'); // {c: number, d: string } getPropValue(obj, 'a.b.d'); // string getPropValue(obj, 'a.b.x'); // unknown getPropValue(obj, s); // unknown +var obj2 = { + name: 'John', + age: 42, + cars: [ + { make: 'Ford', age: 10 }, + { make: 'Trabant', age: 35 } + ] +}; +var make = getProp2(obj2, 'cars.1.make'); // 'Trabant' //// [templateLiteralTypes1.d.ts] @@ -468,3 +499,18 @@ declare type AA = [ true ] extends [IsNegative, IsNegative] ? 'Every thing is ok!' : ['strange', IsNegative, IsNegative]; declare type BB = AA<-2, -2>; +declare type PathKeys = T extends readonly any[] ? Extract | SubKeys> : T extends object ? Extract | SubKeys> : never; +declare type SubKeys = K extends keyof T ? `${K}.${PathKeys}` : never; +declare function getProp2>(obj: T, path: P): PropType; +declare const obj2: { + readonly name: "John"; + readonly age: 42; + readonly cars: readonly [{ + readonly make: "Ford"; + readonly age: 10; + }, { + readonly make: "Trabant"; + readonly age: 35; + }]; +}; +declare let make: "Trabant"; diff --git a/tests/baselines/reference/templateLiteralTypes1.symbols b/tests/baselines/reference/templateLiteralTypes1.symbols index 4c7162e11355d..58ad67ddc2bd2 100644 --- a/tests/baselines/reference/templateLiteralTypes1.symbols +++ b/tests/baselines/reference/templateLiteralTypes1.symbols @@ -869,3 +869,82 @@ type BB = AA<-2, -2>; >BB : Symbol(BB, Decl(templateLiteralTypes1.ts, 211, 123)) >AA : Symbol(AA, Decl(templateLiteralTypes1.ts, 208, 79)) +// Repro from #40970 + +type PathKeys = +>PathKeys : Symbol(PathKeys, Decl(templateLiteralTypes1.ts, 213, 21)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) + + T extends readonly any[] ? Extract | SubKeys> : +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) +>SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 220, 10)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) + + T extends object ? Extract | SubKeys> : +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) +>SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 220, 10)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) + + never; + +type SubKeys = K extends keyof T ? `${K}.${PathKeys}` : never; +>SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 220, 10)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 222, 13)) +>K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15)) +>K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 222, 13)) +>K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15)) +>PathKeys : Symbol(PathKeys, Decl(templateLiteralTypes1.ts, 213, 21)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 222, 13)) +>K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15)) + +declare function getProp2>(obj: T, path: P): PropType; +>getProp2 : Symbol(getProp2, Decl(templateLiteralTypes1.ts, 222, 89)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26)) +>P : Symbol(P, Decl(templateLiteralTypes1.ts, 224, 28)) +>PathKeys : Symbol(PathKeys, Decl(templateLiteralTypes1.ts, 213, 21)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26)) +>obj : Symbol(obj, Decl(templateLiteralTypes1.ts, 224, 52)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26)) +>path : Symbol(path, Decl(templateLiteralTypes1.ts, 224, 59)) +>P : Symbol(P, Decl(templateLiteralTypes1.ts, 224, 28)) +>PropType : Symbol(PropType, Decl(templateLiteralTypes1.ts, 138, 69)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26)) +>P : Symbol(P, Decl(templateLiteralTypes1.ts, 224, 28)) + +const obj2 = { +>obj2 : Symbol(obj2, Decl(templateLiteralTypes1.ts, 226, 5)) + + name: 'John', +>name : Symbol(name, Decl(templateLiteralTypes1.ts, 226, 14)) + + age: 42, +>age : Symbol(age, Decl(templateLiteralTypes1.ts, 227, 17)) + + cars: [ +>cars : Symbol(cars, Decl(templateLiteralTypes1.ts, 228, 12)) + + { make: 'Ford', age: 10 }, +>make : Symbol(make, Decl(templateLiteralTypes1.ts, 230, 9)) +>age : Symbol(age, Decl(templateLiteralTypes1.ts, 230, 23)) + + { make: 'Trabant', age: 35 } +>make : Symbol(make, Decl(templateLiteralTypes1.ts, 231, 9)) +>age : Symbol(age, Decl(templateLiteralTypes1.ts, 231, 26)) + + ] +} as const; + +let make = getProp2(obj2, 'cars.1.make'); // 'Trabant' +>make : Symbol(make, Decl(templateLiteralTypes1.ts, 235, 3)) +>getProp2 : Symbol(getProp2, Decl(templateLiteralTypes1.ts, 222, 89)) +>obj2 : Symbol(obj2, Decl(templateLiteralTypes1.ts, 226, 5)) + diff --git a/tests/baselines/reference/templateLiteralTypes1.types b/tests/baselines/reference/templateLiteralTypes1.types index 687b03eb17bb8..3ab499d448bd1 100644 --- a/tests/baselines/reference/templateLiteralTypes1.types +++ b/tests/baselines/reference/templateLiteralTypes1.types @@ -536,3 +536,61 @@ type BB = AA<-2, -2>; >-2 : -2 >2 : 2 +// Repro from #40970 + +type PathKeys = +>PathKeys : PathKeys + + T extends readonly any[] ? Extract | SubKeys> : + T extends object ? Extract | SubKeys> : + never; + +type SubKeys = K extends keyof T ? `${K}.${PathKeys}` : never; +>SubKeys : SubKeys + +declare function getProp2>(obj: T, path: P): PropType; +>getProp2 : >(obj: T, path: P) => PropType +>obj : T +>path : P + +const obj2 = { +>obj2 : { readonly name: "John"; readonly age: 42; readonly cars: readonly [{ readonly make: "Ford"; readonly age: 10; }, { readonly make: "Trabant"; readonly age: 35; }]; } +>{ name: 'John', age: 42, cars: [ { make: 'Ford', age: 10 }, { make: 'Trabant', age: 35 } ]} as const : { readonly name: "John"; readonly age: 42; readonly cars: readonly [{ readonly make: "Ford"; readonly age: 10; }, { readonly make: "Trabant"; readonly age: 35; }]; } +>{ name: 'John', age: 42, cars: [ { make: 'Ford', age: 10 }, { make: 'Trabant', age: 35 } ]} : { readonly name: "John"; readonly age: 42; readonly cars: readonly [{ readonly make: "Ford"; readonly age: 10; }, { readonly make: "Trabant"; readonly age: 35; }]; } + + name: 'John', +>name : "John" +>'John' : "John" + + age: 42, +>age : 42 +>42 : 42 + + cars: [ +>cars : readonly [{ readonly make: "Ford"; readonly age: 10; }, { readonly make: "Trabant"; readonly age: 35; }] +>[ { make: 'Ford', age: 10 }, { make: 'Trabant', age: 35 } ] : readonly [{ readonly make: "Ford"; readonly age: 10; }, { readonly make: "Trabant"; readonly age: 35; }] + + { make: 'Ford', age: 10 }, +>{ make: 'Ford', age: 10 } : { readonly make: "Ford"; readonly age: 10; } +>make : "Ford" +>'Ford' : "Ford" +>age : 10 +>10 : 10 + + { make: 'Trabant', age: 35 } +>{ make: 'Trabant', age: 35 } : { readonly make: "Trabant"; readonly age: 35; } +>make : "Trabant" +>'Trabant' : "Trabant" +>age : 35 +>35 : 35 + + ] +} as const; + +let make = getProp2(obj2, 'cars.1.make'); // 'Trabant' +>make : "Trabant" +>getProp2(obj2, 'cars.1.make') : "Trabant" +>getProp2 : >(obj: T, path: P) => PropType +>obj2 : { readonly name: "John"; readonly age: 42; readonly cars: readonly [{ readonly make: "Ford"; readonly age: 10; }, { readonly make: "Trabant"; readonly age: 35; }]; } +>'cars.1.make' : "cars.1.make" + From 56dc16e6a52d15e3e75a8a95806da4d927f75a84 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 6 Oct 2020 15:59:12 -0700 Subject: [PATCH 5/5] Fix lint issue --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f743627c4d9a8..eba0e20d99b55 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10917,7 +10917,7 @@ namespace ts { return type.resolvedBaseConstraint; } let nonTerminating = false; - let stack: Type[] = []; + const stack: Type[] = []; return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type); function getImmediateBaseConstraint(t: Type): Type {