Skip to content

Commit

Permalink
Mark deep indexed access comparisons as expanding (#33144)
Browse files Browse the repository at this point in the history
* mark deep indexed accesses as deeply nested in comparisons

* Add test derived from lodash example
  • Loading branch information
weswigham committed Sep 5, 2019
1 parent 2b153fc commit 72bb4c2
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/compiler/checker.ts
Expand Up @@ -14549,6 +14549,9 @@ namespace ts {
// though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely
// expanding. Effectively, we will generate a false positive when two types are structurally equal to at least 5
// levels, but unequal at some level beyond that.
// In addition, this will also detect when an indexed access has been chained off of 5 or more times (which is essentially
// the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding false positives
// for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`).
function isDeeplyNestedType(type: Type, stack: Type[], depth: number): boolean {
// We track all object types that have an associated symbol (representing the origin of the type)
if (depth >= 5 && type.flags & TypeFlags.Object) {
Expand All @@ -14564,9 +14567,31 @@ namespace ts {
}
}
}
if (depth >= 5 && type.flags & TypeFlags.IndexedAccess) {
const root = getRootObjectTypeFromIndexedAccessChain(type);
let count = 0;
for (let i = 0; i < depth; i++) {
const t = stack[i];
if (getRootObjectTypeFromIndexedAccessChain(t) === root) {
count++;
if (count >= 5) return true;
}
}
}
return false;
}

/**
* Gets the leftmost object type in a chain of indexed accesses, eg, in A[P][Q], returns A
*/
function getRootObjectTypeFromIndexedAccessChain(type: Type) {
let t = type;
while (t.flags & TypeFlags.IndexedAccess) {
t = (t as IndexedAccessType).objectType;
}
return t;
}

function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean {
return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False;
}
Expand Down
@@ -0,0 +1,17 @@
//// [comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts]
type PartialDeep<T> = {[K in keyof T]?: PartialDeep<T[K]>};
type Many<T> = T | readonly T[];

interface Collection<T> {
sortBy(...iteratees: Many<PartialDeep<T>>[]): Collection<T>;
}

const x: Collection<{x: number}> = (null as any as Collection<{x: number, y: number}>);

export {};


//// [comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.js]
"use strict";
exports.__esModule = true;
var x = null;
@@ -0,0 +1,40 @@
=== tests/cases/compiler/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts ===
type PartialDeep<T> = {[K in keyof T]?: PartialDeep<T[K]>};
>PartialDeep : Symbol(PartialDeep, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 0))
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 17))
>K : Symbol(K, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 24))
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 17))
>PartialDeep : Symbol(PartialDeep, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 0))
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 17))
>K : Symbol(K, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 24))

type Many<T> = T | readonly T[];
>Many : Symbol(Many, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 59))
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 10))
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 10))
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 10))

interface Collection<T> {
>Collection : Symbol(Collection, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 32))
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 3, 21))

sortBy(...iteratees: Many<PartialDeep<T>>[]): Collection<T>;
>sortBy : Symbol(Collection.sortBy, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 3, 25))
>iteratees : Symbol(iteratees, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 4, 11))
>Many : Symbol(Many, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 59))
>PartialDeep : Symbol(PartialDeep, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 0, 0))
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 3, 21))
>Collection : Symbol(Collection, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 32))
>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 3, 21))
}

const x: Collection<{x: number}> = (null as any as Collection<{x: number, y: number}>);
>x : Symbol(x, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 7, 5))
>Collection : Symbol(Collection, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 32))
>x : Symbol(x, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 7, 21))
>Collection : Symbol(Collection, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 32))
>x : Symbol(x, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 7, 63))
>y : Symbol(y, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 7, 73))

export {};

@@ -0,0 +1,25 @@
=== tests/cases/compiler/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts ===
type PartialDeep<T> = {[K in keyof T]?: PartialDeep<T[K]>};
>PartialDeep : PartialDeep<T>

type Many<T> = T | readonly T[];
>Many : Many<T>

interface Collection<T> {
sortBy(...iteratees: Many<PartialDeep<T>>[]): Collection<T>;
>sortBy : (...iteratees: Many<PartialDeep<T>>[]) => Collection<T>
>iteratees : Many<PartialDeep<T>>[]
}

const x: Collection<{x: number}> = (null as any as Collection<{x: number, y: number}>);
>x : Collection<{ x: number; }>
>x : number
>(null as any as Collection<{x: number, y: number}>) : Collection<{ x: number; y: number; }>
>null as any as Collection<{x: number, y: number}> : Collection<{ x: number; y: number; }>
>null as any : any
>null : null
>x : number
>y : number

export {};

@@ -0,0 +1,11 @@
// @strict: true
type PartialDeep<T> = {[K in keyof T]?: PartialDeep<T[K]>};
type Many<T> = T | readonly T[];

interface Collection<T> {
sortBy(...iteratees: Many<PartialDeep<T>>[]): Collection<T>;
}

const x: Collection<{x: number}> = (null as any as Collection<{x: number, y: number}>);

export {};

0 comments on commit 72bb4c2

Please sign in to comment.