From 6b72d5d4cae2e94ee6f02d867e10d47be1e18368 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Wed, 4 Sep 2019 17:01:28 -0700 Subject: [PATCH] Cherry-pick PR #33144 into release-3.6 (#33249) Component commits: 028fa8e723 mark deep indexed accesses as deeply nested in comparisons 67fbdfaeb7 Add test derived from lodash example --- src/compiler/checker.ts | 25 ++++++++++++ ...pAndIndexedAccessTerminatesWithoutError.js | 17 ++++++++ ...ndexedAccessTerminatesWithoutError.symbols | 40 +++++++++++++++++++ ...dIndexedAccessTerminatesWithoutError.types | 25 ++++++++++++ ...pAndIndexedAccessTerminatesWithoutError.ts | 11 +++++ 5 files changed, 118 insertions(+) create mode 100644 tests/baselines/reference/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.js create mode 100644 tests/baselines/reference/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.symbols create mode 100644 tests/baselines/reference/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.types create mode 100644 tests/cases/compiler/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9ff22ef02efbc..d85e62987fd93 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14500,6 +14500,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) { @@ -14515,9 +14518,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; } diff --git a/tests/baselines/reference/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.js b/tests/baselines/reference/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.js new file mode 100644 index 0000000000000..d509ae136fa9a --- /dev/null +++ b/tests/baselines/reference/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.js @@ -0,0 +1,17 @@ +//// [comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts] +type PartialDeep = {[K in keyof T]?: PartialDeep}; +type Many = T | readonly T[]; + +interface Collection { + sortBy(...iteratees: Many>[]): Collection; +} + +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; diff --git a/tests/baselines/reference/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.symbols b/tests/baselines/reference/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.symbols new file mode 100644 index 0000000000000..55e387983e52d --- /dev/null +++ b/tests/baselines/reference/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.symbols @@ -0,0 +1,40 @@ +=== tests/cases/compiler/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts === +type PartialDeep = {[K in keyof T]?: PartialDeep}; +>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 | 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 { +>Collection : Symbol(Collection, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 1, 32)) +>T : Symbol(T, Decl(comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts, 3, 21)) + + sortBy(...iteratees: Many>[]): Collection; +>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 {}; + diff --git a/tests/baselines/reference/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.types b/tests/baselines/reference/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.types new file mode 100644 index 0000000000000..edc8e82ee8747 --- /dev/null +++ b/tests/baselines/reference/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.types @@ -0,0 +1,25 @@ +=== tests/cases/compiler/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts === +type PartialDeep = {[K in keyof T]?: PartialDeep}; +>PartialDeep : PartialDeep + +type Many = T | readonly T[]; +>Many : Many + +interface Collection { + sortBy(...iteratees: Many>[]): Collection; +>sortBy : (...iteratees: Many>[]) => Collection +>iteratees : Many>[] +} + +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 {}; + diff --git a/tests/cases/compiler/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts b/tests/cases/compiler/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts new file mode 100644 index 0000000000000..4dc45c54005ee --- /dev/null +++ b/tests/cases/compiler/comparisonOfPartialDeepAndIndexedAccessTerminatesWithoutError.ts @@ -0,0 +1,11 @@ +// @strict: true +type PartialDeep = {[K in keyof T]?: PartialDeep}; +type Many = T | readonly T[]; + +interface Collection { + sortBy(...iteratees: Many>[]): Collection; +} + +const x: Collection<{x: number}> = (null as any as Collection<{x: number, y: number}>); + +export {};