Skip to content

Commit f342209

Browse files
authored
Add isDeeplyNestedType logic to getResolvedBaseConstraint (#40971)
* Add isDeeplyNestedType logic to getResolvedBaseConstraint * Accept new baselines * Add regression test * Accept new baselines * Fix lint issue
1 parent 14c7316 commit f342209

8 files changed

+242
-13
lines changed

src/compiler/checker.ts

+13-5
Original file line numberDiff line numberDiff line change
@@ -10913,9 +10913,12 @@ namespace ts {
1091310913
* circularly references the type variable.
1091410914
*/
1091510915
function getResolvedBaseConstraint(type: InstantiableType | UnionOrIntersectionType): Type {
10916+
if (type.resolvedBaseConstraint) {
10917+
return type.resolvedBaseConstraint;
10918+
}
1091610919
let nonTerminating = false;
10917-
return type.resolvedBaseConstraint ||
10918-
(type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type));
10920+
const stack: Type[] = [];
10921+
return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type);
1091910922

1092010923
function getImmediateBaseConstraint(t: Type): Type {
1092110924
if (!t.immediateBaseConstraint) {
@@ -10932,9 +10935,14 @@ namespace ts {
1093210935
nonTerminating = true;
1093310936
return t.immediateBaseConstraint = noConstraintType;
1093410937
}
10935-
constraintDepth++;
10936-
let result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false));
10937-
constraintDepth--;
10938+
let result;
10939+
if (!isDeeplyNestedType(t, stack, stack.length)) {
10940+
stack.push(t);
10941+
constraintDepth++;
10942+
result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false));
10943+
constraintDepth--;
10944+
stack.pop();
10945+
}
1093810946
if (!popTypeResolution()) {
1093910947
if (t.flags & TypeFlags.TypeParameter) {
1094010948
const errorNode = getConstraintDeclaration(<TypeParameter>t);

tests/baselines/reference/infiniteConstraints.errors.txt

+1-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' ca
22
tests/cases/compiler/infiniteConstraints.ts(31,43): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
33
tests/cases/compiler/infiniteConstraints.ts(31,63): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
44
tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.
5-
tests/cases/compiler/infiniteConstraints.ts(48,16): error TS2589: Type instantiation is excessively deep and possibly infinite.
65

76

8-
==== tests/cases/compiler/infiniteConstraints.ts (5 errors) ====
7+
==== tests/cases/compiler/infiniteConstraints.ts (4 errors) ====
98
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint
109

1110
type T1<B extends { [K in keyof B]: Extract<B[Exclude<keyof B, K>], { val: string }>["val"] }> = B;
@@ -64,6 +63,4 @@ tests/cases/compiler/infiniteConstraints.ts(48,16): error TS2589: Type instantia
6463

6564
type Conv<T, U = T> =
6665
{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];
67-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68-
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
6966

tests/baselines/reference/recursiveConditionalTypes.errors.txt

+1-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ tests/cases/compiler/recursiveConditionalTypes.ts(50,5): error TS2322: Type 'Tup
2121
Type 'number extends N ? number[] : _TupleOf<number, N, []>' is not assignable to type 'TupleOf<number, M>'.
2222
Type 'number[] | _TupleOf<number, N, []>' is not assignable to type 'TupleOf<number, M>'.
2323
Type 'number[]' is not assignable to type 'TupleOf<number, M>'.
24-
tests/cases/compiler/recursiveConditionalTypes.ts(116,5): error TS2589: Type instantiation is excessively deep and possibly infinite.
2524
tests/cases/compiler/recursiveConditionalTypes.ts(116,9): error TS2345: Argument of type 'Grow2<[], T>' is not assignable to parameter of type 'Grow1<[], T>'.
2625
Type '[] | Grow2<[string], T>' is not assignable to type 'Grow1<[], T>'.
2726
Type '[]' is not assignable to type 'Grow1<[], T>'.
@@ -32,7 +31,7 @@ tests/cases/compiler/recursiveConditionalTypes.ts(116,9): error TS2345: Argument
3231
Type 'string' is not assignable to type 'number'.
3332

3433

35-
==== tests/cases/compiler/recursiveConditionalTypes.ts (10 errors) ====
34+
==== tests/cases/compiler/recursiveConditionalTypes.ts (9 errors) ====
3635
// Awaiting promises
3736

3837
type Awaited<T> =
@@ -180,8 +179,6 @@ tests/cases/compiler/recursiveConditionalTypes.ts(116,9): error TS2345: Argument
180179

181180
function f21<T extends number>(x: Grow1<[], T>, y: Grow2<[], T>) {
182181
f21(y, x); // Error
183-
~~~~~~~~~
184-
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
185182
~
186183
!!! error TS2345: Argument of type 'Grow2<[], T>' is not assignable to parameter of type 'Grow1<[], T>'.
187184
!!! error TS2345: Type '[] | Grow2<[string], T>' is not assignable to type 'Grow1<[], T>'.

tests/baselines/reference/templateLiteralTypes1.errors.txt

+22
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,26 @@ tests/cases/conformance/types/literal/templateLiteralTypes1.ts(205,16): error TS
241241
[true, true] extends [IsNegative<T>, IsNegative<Q>] ? 'Every thing is ok!' : ['strange', IsNegative<T>, IsNegative<Q>];
242242

243243
type BB = AA<-2, -2>;
244+
245+
// Repro from #40970
246+
247+
type PathKeys<T> =
248+
T extends readonly any[] ? Extract<keyof T, `${number}`> | SubKeys<T, Extract<keyof T, `${number}`>> :
249+
T extends object ? Extract<keyof T, string> | SubKeys<T, Extract<keyof T, string>> :
250+
never;
251+
252+
type SubKeys<T, K extends string> = K extends keyof T ? `${K}.${PathKeys<T[K]>}` : never;
253+
254+
declare function getProp2<T, P extends PathKeys<T>>(obj: T, path: P): PropType<T, P>;
255+
256+
const obj2 = {
257+
name: 'John',
258+
age: 42,
259+
cars: [
260+
{ make: 'Ford', age: 10 },
261+
{ make: 'Trabant', age: 35 }
262+
]
263+
} as const;
264+
265+
let make = getProp2(obj2, 'cars.1.make'); // 'Trabant'
244266

tests/baselines/reference/templateLiteralTypes1.js

+46
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,28 @@ type AA<T extends number, Q extends number> =
213213
[true, true] extends [IsNegative<T>, IsNegative<Q>] ? 'Every thing is ok!' : ['strange', IsNegative<T>, IsNegative<Q>];
214214

215215
type BB = AA<-2, -2>;
216+
217+
// Repro from #40970
218+
219+
type PathKeys<T> =
220+
T extends readonly any[] ? Extract<keyof T, `${number}`> | SubKeys<T, Extract<keyof T, `${number}`>> :
221+
T extends object ? Extract<keyof T, string> | SubKeys<T, Extract<keyof T, string>> :
222+
never;
223+
224+
type SubKeys<T, K extends string> = K extends keyof T ? `${K}.${PathKeys<T[K]>}` : never;
225+
226+
declare function getProp2<T, P extends PathKeys<T>>(obj: T, path: P): PropType<T, P>;
227+
228+
const obj2 = {
229+
name: 'John',
230+
age: 42,
231+
cars: [
232+
{ make: 'Ford', age: 10 },
233+
{ make: 'Trabant', age: 35 }
234+
]
235+
} as const;
236+
237+
let make = getProp2(obj2, 'cars.1.make'); // 'Trabant'
216238
217239
218240
//// [templateLiteralTypes1.js]
@@ -243,6 +265,15 @@ getPropValue(obj, 'a.b'); // {c: number, d: string }
243265
getPropValue(obj, 'a.b.d'); // string
244266
getPropValue(obj, 'a.b.x'); // unknown
245267
getPropValue(obj, s); // unknown
268+
var obj2 = {
269+
name: 'John',
270+
age: 42,
271+
cars: [
272+
{ make: 'Ford', age: 10 },
273+
{ make: 'Trabant', age: 35 }
274+
]
275+
};
276+
var make = getProp2(obj2, 'cars.1.make'); // 'Trabant'
246277

247278

248279
//// [templateLiteralTypes1.d.ts]
@@ -468,3 +499,18 @@ declare type AA<T extends number, Q extends number> = [
468499
true
469500
] extends [IsNegative<T>, IsNegative<Q>] ? 'Every thing is ok!' : ['strange', IsNegative<T>, IsNegative<Q>];
470501
declare type BB = AA<-2, -2>;
502+
declare type PathKeys<T> = T extends readonly any[] ? Extract<keyof T, `${number}`> | SubKeys<T, Extract<keyof T, `${number}`>> : T extends object ? Extract<keyof T, string> | SubKeys<T, Extract<keyof T, string>> : never;
503+
declare type SubKeys<T, K extends string> = K extends keyof T ? `${K}.${PathKeys<T[K]>}` : never;
504+
declare function getProp2<T, P extends PathKeys<T>>(obj: T, path: P): PropType<T, P>;
505+
declare const obj2: {
506+
readonly name: "John";
507+
readonly age: 42;
508+
readonly cars: readonly [{
509+
readonly make: "Ford";
510+
readonly age: 10;
511+
}, {
512+
readonly make: "Trabant";
513+
readonly age: 35;
514+
}];
515+
};
516+
declare let make: "Trabant";

tests/baselines/reference/templateLiteralTypes1.symbols

+79
Original file line numberDiff line numberDiff line change
@@ -869,3 +869,82 @@ type BB = AA<-2, -2>;
869869
>BB : Symbol(BB, Decl(templateLiteralTypes1.ts, 211, 123))
870870
>AA : Symbol(AA, Decl(templateLiteralTypes1.ts, 208, 79))
871871

872+
// Repro from #40970
873+
874+
type PathKeys<T> =
875+
>PathKeys : Symbol(PathKeys, Decl(templateLiteralTypes1.ts, 213, 21))
876+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
877+
878+
T extends readonly any[] ? Extract<keyof T, `${number}`> | SubKeys<T, Extract<keyof T, `${number}`>> :
879+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
880+
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
881+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
882+
>SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 220, 10))
883+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
884+
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
885+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
886+
887+
T extends object ? Extract<keyof T, string> | SubKeys<T, Extract<keyof T, string>> :
888+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
889+
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
890+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
891+
>SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 220, 10))
892+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
893+
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
894+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
895+
896+
never;
897+
898+
type SubKeys<T, K extends string> = K extends keyof T ? `${K}.${PathKeys<T[K]>}` : never;
899+
>SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 220, 10))
900+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 222, 13))
901+
>K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15))
902+
>K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15))
903+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 222, 13))
904+
>K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15))
905+
>PathKeys : Symbol(PathKeys, Decl(templateLiteralTypes1.ts, 213, 21))
906+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 222, 13))
907+
>K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15))
908+
909+
declare function getProp2<T, P extends PathKeys<T>>(obj: T, path: P): PropType<T, P>;
910+
>getProp2 : Symbol(getProp2, Decl(templateLiteralTypes1.ts, 222, 89))
911+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26))
912+
>P : Symbol(P, Decl(templateLiteralTypes1.ts, 224, 28))
913+
>PathKeys : Symbol(PathKeys, Decl(templateLiteralTypes1.ts, 213, 21))
914+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26))
915+
>obj : Symbol(obj, Decl(templateLiteralTypes1.ts, 224, 52))
916+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26))
917+
>path : Symbol(path, Decl(templateLiteralTypes1.ts, 224, 59))
918+
>P : Symbol(P, Decl(templateLiteralTypes1.ts, 224, 28))
919+
>PropType : Symbol(PropType, Decl(templateLiteralTypes1.ts, 138, 69))
920+
>T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26))
921+
>P : Symbol(P, Decl(templateLiteralTypes1.ts, 224, 28))
922+
923+
const obj2 = {
924+
>obj2 : Symbol(obj2, Decl(templateLiteralTypes1.ts, 226, 5))
925+
926+
name: 'John',
927+
>name : Symbol(name, Decl(templateLiteralTypes1.ts, 226, 14))
928+
929+
age: 42,
930+
>age : Symbol(age, Decl(templateLiteralTypes1.ts, 227, 17))
931+
932+
cars: [
933+
>cars : Symbol(cars, Decl(templateLiteralTypes1.ts, 228, 12))
934+
935+
{ make: 'Ford', age: 10 },
936+
>make : Symbol(make, Decl(templateLiteralTypes1.ts, 230, 9))
937+
>age : Symbol(age, Decl(templateLiteralTypes1.ts, 230, 23))
938+
939+
{ make: 'Trabant', age: 35 }
940+
>make : Symbol(make, Decl(templateLiteralTypes1.ts, 231, 9))
941+
>age : Symbol(age, Decl(templateLiteralTypes1.ts, 231, 26))
942+
943+
]
944+
} as const;
945+
946+
let make = getProp2(obj2, 'cars.1.make'); // 'Trabant'
947+
>make : Symbol(make, Decl(templateLiteralTypes1.ts, 235, 3))
948+
>getProp2 : Symbol(getProp2, Decl(templateLiteralTypes1.ts, 222, 89))
949+
>obj2 : Symbol(obj2, Decl(templateLiteralTypes1.ts, 226, 5))
950+

tests/baselines/reference/templateLiteralTypes1.types

+58
Original file line numberDiff line numberDiff line change
@@ -536,3 +536,61 @@ type BB = AA<-2, -2>;
536536
>-2 : -2
537537
>2 : 2
538538

539+
// Repro from #40970
540+
541+
type PathKeys<T> =
542+
>PathKeys : PathKeys<T>
543+
544+
T extends readonly any[] ? Extract<keyof T, `${number}`> | SubKeys<T, Extract<keyof T, `${number}`>> :
545+
T extends object ? Extract<keyof T, string> | SubKeys<T, Extract<keyof T, string>> :
546+
never;
547+
548+
type SubKeys<T, K extends string> = K extends keyof T ? `${K}.${PathKeys<T[K]>}` : never;
549+
>SubKeys : SubKeys<T, K>
550+
551+
declare function getProp2<T, P extends PathKeys<T>>(obj: T, path: P): PropType<T, P>;
552+
>getProp2 : <T, P extends PathKeys<T>>(obj: T, path: P) => PropType<T, P>
553+
>obj : T
554+
>path : P
555+
556+
const obj2 = {
557+
>obj2 : { readonly name: "John"; readonly age: 42; readonly cars: readonly [{ readonly make: "Ford"; readonly age: 10; }, { readonly make: "Trabant"; readonly age: 35; }]; }
558+
>{ 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; }]; }
559+
>{ 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; }]; }
560+
561+
name: 'John',
562+
>name : "John"
563+
>'John' : "John"
564+
565+
age: 42,
566+
>age : 42
567+
>42 : 42
568+
569+
cars: [
570+
>cars : readonly [{ readonly make: "Ford"; readonly age: 10; }, { readonly make: "Trabant"; readonly age: 35; }]
571+
>[ { make: 'Ford', age: 10 }, { make: 'Trabant', age: 35 } ] : readonly [{ readonly make: "Ford"; readonly age: 10; }, { readonly make: "Trabant"; readonly age: 35; }]
572+
573+
{ make: 'Ford', age: 10 },
574+
>{ make: 'Ford', age: 10 } : { readonly make: "Ford"; readonly age: 10; }
575+
>make : "Ford"
576+
>'Ford' : "Ford"
577+
>age : 10
578+
>10 : 10
579+
580+
{ make: 'Trabant', age: 35 }
581+
>{ make: 'Trabant', age: 35 } : { readonly make: "Trabant"; readonly age: 35; }
582+
>make : "Trabant"
583+
>'Trabant' : "Trabant"
584+
>age : 35
585+
>35 : 35
586+
587+
]
588+
} as const;
589+
590+
let make = getProp2(obj2, 'cars.1.make'); // 'Trabant'
591+
>make : "Trabant"
592+
>getProp2(obj2, 'cars.1.make') : "Trabant"
593+
>getProp2 : <T, P extends PathKeys<T>>(obj: T, path: P) => PropType<T, P>
594+
>obj2 : { readonly name: "John"; readonly age: 42; readonly cars: readonly [{ readonly make: "Ford"; readonly age: 10; }, { readonly make: "Trabant"; readonly age: 35; }]; }
595+
>'cars.1.make' : "cars.1.make"
596+

tests/cases/conformance/types/literal/templateLiteralTypes1.ts

+22
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,25 @@ type AA<T extends number, Q extends number> =
215215
[true, true] extends [IsNegative<T>, IsNegative<Q>] ? 'Every thing is ok!' : ['strange', IsNegative<T>, IsNegative<Q>];
216216

217217
type BB = AA<-2, -2>;
218+
219+
// Repro from #40970
220+
221+
type PathKeys<T> =
222+
T extends readonly any[] ? Extract<keyof T, `${number}`> | SubKeys<T, Extract<keyof T, `${number}`>> :
223+
T extends object ? Extract<keyof T, string> | SubKeys<T, Extract<keyof T, string>> :
224+
never;
225+
226+
type SubKeys<T, K extends string> = K extends keyof T ? `${K}.${PathKeys<T[K]>}` : never;
227+
228+
declare function getProp2<T, P extends PathKeys<T>>(obj: T, path: P): PropType<T, P>;
229+
230+
const obj2 = {
231+
name: 'John',
232+
age: 42,
233+
cars: [
234+
{ make: 'Ford', age: 10 },
235+
{ make: 'Trabant', age: 35 }
236+
]
237+
} as const;
238+
239+
let make = getProp2(obj2, 'cars.1.make'); // 'Trabant'

0 commit comments

Comments
 (0)