Skip to content

Commit 72bb4c2

Browse files
authored
Mark deep indexed access comparisons as expanding (#33144)
* mark deep indexed accesses as deeply nested in comparisons * Add test derived from lodash example
1 parent 2b153fc commit 72bb4c2

5 files changed

+118
-0
lines changed

src/compiler/checker.ts

+25
Original file line numberDiff line numberDiff line change
@@ -14549,6 +14549,9 @@ namespace ts {
1454914549
// though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely
1455014550
// expanding. Effectively, we will generate a false positive when two types are structurally equal to at least 5
1455114551
// levels, but unequal at some level beyond that.
14552+
// In addition, this will also detect when an indexed access has been chained off of 5 or more times (which is essentially
14553+
// the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding false positives
14554+
// for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`).
1455214555
function isDeeplyNestedType(type: Type, stack: Type[], depth: number): boolean {
1455314556
// We track all object types that have an associated symbol (representing the origin of the type)
1455414557
if (depth >= 5 && type.flags & TypeFlags.Object) {
@@ -14564,9 +14567,31 @@ namespace ts {
1456414567
}
1456514568
}
1456614569
}
14570+
if (depth >= 5 && type.flags & TypeFlags.IndexedAccess) {
14571+
const root = getRootObjectTypeFromIndexedAccessChain(type);
14572+
let count = 0;
14573+
for (let i = 0; i < depth; i++) {
14574+
const t = stack[i];
14575+
if (getRootObjectTypeFromIndexedAccessChain(t) === root) {
14576+
count++;
14577+
if (count >= 5) return true;
14578+
}
14579+
}
14580+
}
1456714581
return false;
1456814582
}
1456914583

14584+
/**
14585+
* Gets the leftmost object type in a chain of indexed accesses, eg, in A[P][Q], returns A
14586+
*/
14587+
function getRootObjectTypeFromIndexedAccessChain(type: Type) {
14588+
let t = type;
14589+
while (t.flags & TypeFlags.IndexedAccess) {
14590+
t = (t as IndexedAccessType).objectType;
14591+
}
14592+
return t;
14593+
}
14594+
1457014595
function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean {
1457114596
return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False;
1457214597
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//// [comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts]
2+
type PartialDeep<T> = {[K in keyof T]?: PartialDeep<T[K]>};
3+
type Many<T> = T | readonly T[];
4+
5+
interface Collection<T> {
6+
sortBy(...iteratees: Many<PartialDeep<T>>[]): Collection<T>;
7+
}
8+
9+
const x: Collection<{x: number}> = (null as any as Collection<{x: number, y: number}>);
10+
11+
export {};
12+
13+
14+
//// [comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.js]
15+
"use strict";
16+
exports.__esModule = true;
17+
var x = null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
=== tests/cases/compiler/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts ===
2+
type PartialDeep<T> = {[K in keyof T]?: PartialDeep<T[K]>};
3+
>PartialDeep : Symbol(PartialDeep, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 0))
4+
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 17))
5+
>K : Symbol(K, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 24))
6+
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 17))
7+
>PartialDeep : Symbol(PartialDeep, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 0))
8+
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 17))
9+
>K : Symbol(K, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 24))
10+
11+
type Many<T> = T | readonly T[];
12+
>Many : Symbol(Many, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 59))
13+
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 10))
14+
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 10))
15+
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 10))
16+
17+
interface Collection<T> {
18+
>Collection : Symbol(Collection, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 32))
19+
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 3, 21))
20+
21+
sortBy(...iteratees: Many<PartialDeep<T>>[]): Collection<T>;
22+
>sortBy : Symbol(Collection.sortBy, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 3, 25))
23+
>iteratees : Symbol(iteratees, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 4, 11))
24+
>Many : Symbol(Many, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 59))
25+
>PartialDeep : Symbol(PartialDeep, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 0))
26+
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 3, 21))
27+
>Collection : Symbol(Collection, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 32))
28+
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 3, 21))
29+
}
30+
31+
const x: Collection<{x: number}> = (null as any as Collection<{x: number, y: number}>);
32+
>x : Symbol(x, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 7, 5))
33+
>Collection : Symbol(Collection, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 32))
34+
>x : Symbol(x, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 7, 21))
35+
>Collection : Symbol(Collection, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 32))
36+
>x : Symbol(x, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 7, 63))
37+
>y : Symbol(y, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 7, 73))
38+
39+
export {};
40+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
=== tests/cases/compiler/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts ===
2+
type PartialDeep<T> = {[K in keyof T]?: PartialDeep<T[K]>};
3+
>PartialDeep : PartialDeep<T>
4+
5+
type Many<T> = T | readonly T[];
6+
>Many : Many<T>
7+
8+
interface Collection<T> {
9+
sortBy(...iteratees: Many<PartialDeep<T>>[]): Collection<T>;
10+
>sortBy : (...iteratees: Many<PartialDeep<T>>[]) => Collection<T>
11+
>iteratees : Many<PartialDeep<T>>[]
12+
}
13+
14+
const x: Collection<{x: number}> = (null as any as Collection<{x: number, y: number}>);
15+
>x : Collection<{ x: number; }>
16+
>x : number
17+
>(null as any as Collection<{x: number, y: number}>) : Collection<{ x: number; y: number; }>
18+
>null as any as Collection<{x: number, y: number}> : Collection<{ x: number; y: number; }>
19+
>null as any : any
20+
>null : null
21+
>x : number
22+
>y : number
23+
24+
export {};
25+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @strict: true
2+
type PartialDeep<T> = {[K in keyof T]?: PartialDeep<T[K]>};
3+
type Many<T> = T | readonly T[];
4+
5+
interface Collection<T> {
6+
sortBy(...iteratees: Many<PartialDeep<T>>[]): Collection<T>;
7+
}
8+
9+
const x: Collection<{x: number}> = (null as any as Collection<{x: number, y: number}>);
10+
11+
export {};

0 commit comments

Comments
 (0)