From f4fc9ed61f2352e429ff7db24fd39d1c71c0f3c6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 23 Mar 2017 11:23:13 -0700 Subject: [PATCH 1/3] Fix deeply nested type check --- src/compiler/checker.ts | 36 +++++++++++++++++++----------------- src/compiler/types.ts | 1 - 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 24ca7da87c443..3ab09aff04b6e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8277,8 +8277,8 @@ namespace ts { maybeStack[depth].set(id, RelationComparisonResult.Succeeded); depth++; const saveExpandingFlags = expandingFlags; - if (!(expandingFlags & 1) && isDeeplyNestedGeneric(source, sourceStack, depth)) expandingFlags |= 1; - if (!(expandingFlags & 2) && isDeeplyNestedGeneric(target, targetStack, depth)) expandingFlags |= 2; + if (!(expandingFlags & 1) && isDeeplyNestedType(source, sourceStack, depth)) expandingFlags |= 1; + if (!(expandingFlags & 2) && isDeeplyNestedType(target, targetStack, depth)) expandingFlags |= 2; let result: Ternary; if (expandingFlags === 3) { result = Ternary.Maybe; @@ -8698,21 +8698,23 @@ namespace ts { return false; } - // Return true if the given type is part of a deeply nested chain of generic instantiations. We consider this to be the case - // when structural type comparisons have been started for 10 or more instantiations of the same generic type. It is possible, - // 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 10 levels, but unequal at - // some level beyond that. - function isDeeplyNestedGeneric(type: Type, stack: Type[], depth: number): boolean { - // We track type references (created by createTypeReference) and instantiated types (created by instantiateType) - if (getObjectFlags(type) & (ObjectFlags.Reference | ObjectFlags.Instantiated) && depth >= 5) { + // Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons + // for 5 or more occurrences or instantiations of the type have been recorded on the given stack. It is possible, + // 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. + 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) { const symbol = type.symbol; - let count = 0; - for (let i = 0; i < depth; i++) { - const t = stack[i]; - if (getObjectFlags(t) & (ObjectFlags.Reference | ObjectFlags.Instantiated) && t.symbol === symbol) { - count++; - if (count >= 5) return true; + if (symbol) { + let count = 0; + for (let i = 0; i < depth; i++) { + const t = stack[i]; + if (t.flags & TypeFlags.Object && t.symbol === symbol) { + count++; + if (count >= 5) return true; + } } } } @@ -9455,7 +9457,7 @@ namespace ts { if (isInProcess(source, target)) { return; } - if (isDeeplyNestedGeneric(source, sourceStack, depth) && isDeeplyNestedGeneric(target, targetStack, depth)) { + if (isDeeplyNestedType(source, sourceStack, depth) && isDeeplyNestedType(target, targetStack, depth)) { return; } const key = source.id + "," + target.id; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2377cd81f1621..ceeb2922d422e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3014,7 +3014,6 @@ namespace ts { ObjectLiteral = 1 << 7, // Originates in an object literal EvolvingArray = 1 << 8, // Evolving array type ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties - NonPrimitive = 1 << 10, // NonPrimitive object type ClassOrInterface = Class | Interface } From 5ea146334a14abf2f58232a261cea5a83a4652f7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 23 Mar 2017 11:23:26 -0700 Subject: [PATCH 2/3] Add regression test --- tests/cases/compiler/deeplyNestedCheck.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/cases/compiler/deeplyNestedCheck.ts diff --git a/tests/cases/compiler/deeplyNestedCheck.ts b/tests/cases/compiler/deeplyNestedCheck.ts new file mode 100644 index 0000000000000..e95233af98556 --- /dev/null +++ b/tests/cases/compiler/deeplyNestedCheck.ts @@ -0,0 +1,9 @@ +// Repro from #14794 + +interface DataSnapshot { + child(path: string): DataSnapshot; +} + +interface Snapshot extends DataSnapshot { + child(path: U): Snapshot; +} From f139a6c58a6ea8df8f3561d8f342f625f1d4415e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 23 Mar 2017 11:24:01 -0700 Subject: [PATCH 3/3] Accept new baselines --- .../baselines/reference/deeplyNestedCheck.js | 14 +++++++++ .../reference/deeplyNestedCheck.symbols | 29 +++++++++++++++++++ .../reference/deeplyNestedCheck.types | 29 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 tests/baselines/reference/deeplyNestedCheck.js create mode 100644 tests/baselines/reference/deeplyNestedCheck.symbols create mode 100644 tests/baselines/reference/deeplyNestedCheck.types diff --git a/tests/baselines/reference/deeplyNestedCheck.js b/tests/baselines/reference/deeplyNestedCheck.js new file mode 100644 index 0000000000000..466ae94f12d98 --- /dev/null +++ b/tests/baselines/reference/deeplyNestedCheck.js @@ -0,0 +1,14 @@ +//// [deeplyNestedCheck.ts] +// Repro from #14794 + +interface DataSnapshot { + child(path: string): DataSnapshot; +} + +interface Snapshot extends DataSnapshot { + child(path: U): Snapshot; +} + + +//// [deeplyNestedCheck.js] +// Repro from #14794 diff --git a/tests/baselines/reference/deeplyNestedCheck.symbols b/tests/baselines/reference/deeplyNestedCheck.symbols new file mode 100644 index 0000000000000..a8b5af608806e --- /dev/null +++ b/tests/baselines/reference/deeplyNestedCheck.symbols @@ -0,0 +1,29 @@ +=== tests/cases/compiler/deeplyNestedCheck.ts === +// Repro from #14794 + +interface DataSnapshot { +>DataSnapshot : Symbol(DataSnapshot, Decl(deeplyNestedCheck.ts, 0, 0)) +>X : Symbol(X, Decl(deeplyNestedCheck.ts, 2, 23)) + + child(path: string): DataSnapshot; +>child : Symbol(DataSnapshot.child, Decl(deeplyNestedCheck.ts, 2, 32)) +>path : Symbol(path, Decl(deeplyNestedCheck.ts, 3, 8)) +>DataSnapshot : Symbol(DataSnapshot, Decl(deeplyNestedCheck.ts, 0, 0)) +} + +interface Snapshot extends DataSnapshot { +>Snapshot : Symbol(Snapshot, Decl(deeplyNestedCheck.ts, 4, 1)) +>T : Symbol(T, Decl(deeplyNestedCheck.ts, 6, 19)) +>DataSnapshot : Symbol(DataSnapshot, Decl(deeplyNestedCheck.ts, 0, 0)) + + child(path: U): Snapshot; +>child : Symbol(Snapshot.child, Decl(deeplyNestedCheck.ts, 6, 44)) +>U : Symbol(U, Decl(deeplyNestedCheck.ts, 7, 8)) +>T : Symbol(T, Decl(deeplyNestedCheck.ts, 6, 19)) +>path : Symbol(path, Decl(deeplyNestedCheck.ts, 7, 27)) +>U : Symbol(U, Decl(deeplyNestedCheck.ts, 7, 8)) +>Snapshot : Symbol(Snapshot, Decl(deeplyNestedCheck.ts, 4, 1)) +>T : Symbol(T, Decl(deeplyNestedCheck.ts, 6, 19)) +>U : Symbol(U, Decl(deeplyNestedCheck.ts, 7, 8)) +} + diff --git a/tests/baselines/reference/deeplyNestedCheck.types b/tests/baselines/reference/deeplyNestedCheck.types new file mode 100644 index 0000000000000..e500fc75ed164 --- /dev/null +++ b/tests/baselines/reference/deeplyNestedCheck.types @@ -0,0 +1,29 @@ +=== tests/cases/compiler/deeplyNestedCheck.ts === +// Repro from #14794 + +interface DataSnapshot { +>DataSnapshot : DataSnapshot +>X : X + + child(path: string): DataSnapshot; +>child : (path: string) => DataSnapshot<{}> +>path : string +>DataSnapshot : DataSnapshot +} + +interface Snapshot extends DataSnapshot { +>Snapshot : Snapshot +>T : T +>DataSnapshot : DataSnapshot + + child(path: U): Snapshot; +>child : (path: U) => Snapshot +>U : U +>T : T +>path : U +>U : U +>Snapshot : Snapshot +>T : T +>U : U +} +