diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 76e420e0913..08a6576e5cf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4439,8 +4439,8 @@ namespace ts { maybeStack[depth][id] = RelationComparisonResult.Succeeded; depth++; let saveExpandingFlags = expandingFlags; - if (!(expandingFlags & 1) && isDeeplyNestedGeneric(source, sourceStack)) expandingFlags |= 1; - if (!(expandingFlags & 2) && isDeeplyNestedGeneric(target, targetStack)) expandingFlags |= 2; + if (!(expandingFlags & 1) && isDeeplyNestedGeneric(source, sourceStack, depth)) expandingFlags |= 1; + if (!(expandingFlags & 2) && isDeeplyNestedGeneric(target, targetStack, depth)) expandingFlags |= 2; let result: Ternary; if (expandingFlags === 3) { result = Ternary.Maybe; @@ -4476,27 +4476,6 @@ namespace ts { return result; } - // 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: ObjectType, stack: ObjectType[]): boolean { - // We track type references (created by createTypeReference) and instantiated types (created by instantiateType) - if (type.flags & (TypeFlags.Reference | TypeFlags.Instantiated) && depth >= 10) { - let symbol = type.symbol; - let count = 0; - for (let i = 0; i < depth; i++) { - let t = stack[i]; - if (t.flags & (TypeFlags.Reference | TypeFlags.Instantiated) && t.symbol === symbol) { - count++; - if (count >= 10) return true; - } - } - } - return false; - } - function propertiesRelatedTo(source: ObjectType, target: ObjectType, reportErrors: boolean): Ternary { if (relation === identityRelation) { return propertiesIdenticalTo(source, target); @@ -4815,6 +4794,27 @@ namespace ts { } } + // 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 (type.flags & (TypeFlags.Reference | TypeFlags.Instantiated) && depth >= 5) { + let symbol = type.symbol; + let count = 0; + for (let i = 0; i < depth; i++) { + let t = stack[i]; + if (t.flags & (TypeFlags.Reference | TypeFlags.Instantiated) && t.symbol === symbol) { + count++; + if (count >= 5) return true; + } + } + } + return false; + } + function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean { return compareProperties(sourceProp, targetProp, compareTypes) !== Ternary.False; } @@ -5129,21 +5129,6 @@ namespace ts { return false; } - function isWithinDepthLimit(type: Type, stack: Type[]) { - if (depth >= 5) { - let target = (type).target; - let count = 0; - for (let i = 0; i < depth; i++) { - let t = stack[i]; - if (t.flags & TypeFlags.Reference && (t).target === target) { - count++; - } - } - return count < 5; - } - return true; - } - function inferFromTypes(source: Type, target: Type) { if (source === anyFunctionType) { return; @@ -5211,22 +5196,27 @@ namespace ts { else if (source.flags & TypeFlags.ObjectType && (target.flags & (TypeFlags.Reference | TypeFlags.Tuple) || (target.flags & TypeFlags.Anonymous) && target.symbol && target.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral))) { // If source is an object type, and target is a type reference, a tuple type, the type of a method, or a type literal, infer from members - if (!isInProcess(source, target) && isWithinDepthLimit(source, sourceStack) && isWithinDepthLimit(target, targetStack)) { - if (depth === 0) { - sourceStack = []; - targetStack = []; - } - sourceStack[depth] = source; - targetStack[depth] = target; - depth++; - inferFromProperties(source, target); - inferFromSignatures(source, target, SignatureKind.Call); - inferFromSignatures(source, target, SignatureKind.Construct); - inferFromIndexTypes(source, target, IndexKind.String, IndexKind.String); - inferFromIndexTypes(source, target, IndexKind.Number, IndexKind.Number); - inferFromIndexTypes(source, target, IndexKind.String, IndexKind.Number); - depth--; + if (isInProcess(source, target)) { + return; } + if (isDeeplyNestedGeneric(source, sourceStack, depth) && isDeeplyNestedGeneric(target, targetStack, depth)) { + return; + } + + if (depth === 0) { + sourceStack = []; + targetStack = []; + } + sourceStack[depth] = source; + targetStack[depth] = target; + depth++; + inferFromProperties(source, target); + inferFromSignatures(source, target, SignatureKind.Call); + inferFromSignatures(source, target, SignatureKind.Construct); + inferFromIndexTypes(source, target, IndexKind.String, IndexKind.String); + inferFromIndexTypes(source, target, IndexKind.Number, IndexKind.Number); + inferFromIndexTypes(source, target, IndexKind.String, IndexKind.Number); + depth--; } } diff --git a/tests/baselines/reference/cyclicGenericTypeInstantiationInference.js b/tests/baselines/reference/cyclicGenericTypeInstantiationInference.js new file mode 100644 index 00000000000..4e093fecf79 --- /dev/null +++ b/tests/baselines/reference/cyclicGenericTypeInstantiationInference.js @@ -0,0 +1,39 @@ +//// [cyclicGenericTypeInstantiationInference.ts] +function foo() { + var z = foo(); + var y: { + y2: typeof z + }; + return y; +} + + +function bar() { + var z = bar(); + var y: { + y2: typeof z; + } + return y; +} + +var a = foo(); +var b = bar(); + +function test(x: typeof a): void { } +test(b); + +//// [cyclicGenericTypeInstantiationInference.js] +function foo() { + var z = foo(); + var y; + return y; +} +function bar() { + var z = bar(); + var y; + return y; +} +var a = foo(); +var b = bar(); +function test(x) { } +test(b); diff --git a/tests/baselines/reference/cyclicGenericTypeInstantiationInference.symbols b/tests/baselines/reference/cyclicGenericTypeInstantiationInference.symbols new file mode 100644 index 00000000000..962a1e12cc5 --- /dev/null +++ b/tests/baselines/reference/cyclicGenericTypeInstantiationInference.symbols @@ -0,0 +1,61 @@ +=== tests/cases/compiler/cyclicGenericTypeInstantiationInference.ts === +function foo() { +>foo : Symbol(foo, Decl(cyclicGenericTypeInstantiationInference.ts, 0, 0)) +>T : Symbol(T, Decl(cyclicGenericTypeInstantiationInference.ts, 0, 13)) + + var z = foo(); +>z : Symbol(z, Decl(cyclicGenericTypeInstantiationInference.ts, 1, 7)) +>foo : Symbol(foo, Decl(cyclicGenericTypeInstantiationInference.ts, 0, 0)) +>y : Symbol(y, Decl(cyclicGenericTypeInstantiationInference.ts, 2, 7)) + + var y: { +>y : Symbol(y, Decl(cyclicGenericTypeInstantiationInference.ts, 2, 7)) + + y2: typeof z +>y2 : Symbol(y2, Decl(cyclicGenericTypeInstantiationInference.ts, 2, 12)) +>z : Symbol(z, Decl(cyclicGenericTypeInstantiationInference.ts, 1, 7)) + + }; + return y; +>y : Symbol(y, Decl(cyclicGenericTypeInstantiationInference.ts, 2, 7)) +} + + +function bar() { +>bar : Symbol(bar, Decl(cyclicGenericTypeInstantiationInference.ts, 6, 1)) +>T : Symbol(T, Decl(cyclicGenericTypeInstantiationInference.ts, 9, 13)) + + var z = bar(); +>z : Symbol(z, Decl(cyclicGenericTypeInstantiationInference.ts, 10, 7)) +>bar : Symbol(bar, Decl(cyclicGenericTypeInstantiationInference.ts, 6, 1)) +>y : Symbol(y, Decl(cyclicGenericTypeInstantiationInference.ts, 11, 7)) + + var y: { +>y : Symbol(y, Decl(cyclicGenericTypeInstantiationInference.ts, 11, 7)) + + y2: typeof z; +>y2 : Symbol(y2, Decl(cyclicGenericTypeInstantiationInference.ts, 11, 12)) +>z : Symbol(z, Decl(cyclicGenericTypeInstantiationInference.ts, 10, 7)) + } + return y; +>y : Symbol(y, Decl(cyclicGenericTypeInstantiationInference.ts, 11, 7)) +} + +var a = foo(); +>a : Symbol(a, Decl(cyclicGenericTypeInstantiationInference.ts, 17, 3)) +>foo : Symbol(foo, Decl(cyclicGenericTypeInstantiationInference.ts, 0, 0)) + +var b = bar(); +>b : Symbol(b, Decl(cyclicGenericTypeInstantiationInference.ts, 18, 3)) +>bar : Symbol(bar, Decl(cyclicGenericTypeInstantiationInference.ts, 6, 1)) + +function test(x: typeof a): void { } +>test : Symbol(test, Decl(cyclicGenericTypeInstantiationInference.ts, 18, 22)) +>T : Symbol(T, Decl(cyclicGenericTypeInstantiationInference.ts, 20, 14)) +>x : Symbol(x, Decl(cyclicGenericTypeInstantiationInference.ts, 20, 17)) +>a : Symbol(a, Decl(cyclicGenericTypeInstantiationInference.ts, 17, 3)) + +test(b); +>test : Symbol(test, Decl(cyclicGenericTypeInstantiationInference.ts, 18, 22)) +>b : Symbol(b, Decl(cyclicGenericTypeInstantiationInference.ts, 18, 3)) + diff --git a/tests/baselines/reference/cyclicGenericTypeInstantiationInference.types b/tests/baselines/reference/cyclicGenericTypeInstantiationInference.types new file mode 100644 index 00000000000..765e8349716 --- /dev/null +++ b/tests/baselines/reference/cyclicGenericTypeInstantiationInference.types @@ -0,0 +1,66 @@ +=== tests/cases/compiler/cyclicGenericTypeInstantiationInference.ts === +function foo() { +>foo : () => { y2: any; } +>T : T + + var z = foo(); +>z : { y2: any; } +>foo() : { y2: any; } +>foo : () => { y2: any; } +>y : { y2: any; } + + var y: { +>y : { y2: any; } + + y2: typeof z +>y2 : { y2: any; } +>z : { y2: any; } + + }; + return y; +>y : { y2: any; } +} + + +function bar() { +>bar : () => { y2: any; } +>T : T + + var z = bar(); +>z : { y2: any; } +>bar() : { y2: any; } +>bar : () => { y2: any; } +>y : { y2: any; } + + var y: { +>y : { y2: any; } + + y2: typeof z; +>y2 : { y2: any; } +>z : { y2: any; } + } + return y; +>y : { y2: any; } +} + +var a = foo(); +>a : { y2: any; } +>foo() : { y2: any; } +>foo : () => { y2: any; } + +var b = bar(); +>b : { y2: any; } +>bar() : { y2: any; } +>bar : () => { y2: any; } + +function test(x: typeof a): void { } +>test : (x: { y2: any; }) => void +>T : T +>x : { y2: any; } +>a : { y2: any; } + +test(b); +>test(b) : void +>test : (x: { y2: any; }) => void +>b : { y2: any; } + diff --git a/tests/cases/compiler/cyclicGenericTypeInstantiationInference.ts b/tests/cases/compiler/cyclicGenericTypeInstantiationInference.ts new file mode 100644 index 00000000000..92bc664a44f --- /dev/null +++ b/tests/cases/compiler/cyclicGenericTypeInstantiationInference.ts @@ -0,0 +1,22 @@ +function foo() { + var z = foo(); + var y: { + y2: typeof z + }; + return y; +} + + +function bar() { + var z = bar(); + var y: { + y2: typeof z; + } + return y; +} + +var a = foo(); +var b = bar(); + +function test(x: typeof a): void { } +test(b); \ No newline at end of file