Skip to content

Type argument inference fix for infinitely recursive anonymous types #3452

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 12, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 43 additions & 53 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -5129,21 +5129,6 @@ namespace ts {
return false;
}

function isWithinDepthLimit(type: Type, stack: Type[]) {
if (depth >= 5) {
let target = (<TypeReference>type).target;
let count = 0;
for (let i = 0; i < depth; i++) {
let t = stack[i];
if (t.flags & TypeFlags.Reference && (<TypeReference>t).target === target) {
count++;
}
}
return count < 5;
}
return true;
}

function inferFromTypes(source: Type, target: Type) {
if (source === anyFunctionType) {
return;
Expand Down Expand Up @@ -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--;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is with this brace?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a white space management error


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//// [cyclicGenericTypeInstantiationInference.ts]
function foo<T>() {
var z = foo<typeof y>();
var y: {
y2: typeof z
};
return y;
}


function bar<T>() {
var z = bar<typeof y>();
var y: {
y2: typeof z;
}
return y;
}

var a = foo<number>();
var b = bar<number>();

function test<T>(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);
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
=== tests/cases/compiler/cyclicGenericTypeInstantiationInference.ts ===
function foo<T>() {
>foo : Symbol(foo, Decl(cyclicGenericTypeInstantiationInference.ts, 0, 0))
>T : Symbol(T, Decl(cyclicGenericTypeInstantiationInference.ts, 0, 13))

var z = foo<typeof y>();
>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<T>() {
>bar : Symbol(bar, Decl(cyclicGenericTypeInstantiationInference.ts, 6, 1))
>T : Symbol(T, Decl(cyclicGenericTypeInstantiationInference.ts, 9, 13))

var z = bar<typeof y>();
>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<number>();
>a : Symbol(a, Decl(cyclicGenericTypeInstantiationInference.ts, 17, 3))
>foo : Symbol(foo, Decl(cyclicGenericTypeInstantiationInference.ts, 0, 0))

var b = bar<number>();
>b : Symbol(b, Decl(cyclicGenericTypeInstantiationInference.ts, 18, 3))
>bar : Symbol(bar, Decl(cyclicGenericTypeInstantiationInference.ts, 6, 1))

function test<T>(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))

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
=== tests/cases/compiler/cyclicGenericTypeInstantiationInference.ts ===
function foo<T>() {
>foo : <T>() => { y2: any; }
>T : T

var z = foo<typeof y>();
>z : { y2: any; }
>foo<typeof y>() : { y2: any; }
>foo : <T>() => { 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<T>() {
>bar : <T>() => { y2: any; }
>T : T

var z = bar<typeof y>();
>z : { y2: any; }
>bar<typeof y>() : { y2: any; }
>bar : <T>() => { 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<number>();
>a : { y2: any; }
>foo<number>() : { y2: any; }
>foo : <T>() => { y2: any; }

var b = bar<number>();
>b : { y2: any; }
>bar<number>() : { y2: any; }
>bar : <T>() => { y2: any; }

function test<T>(x: typeof a): void { }
>test : <T>(x: { y2: any; }) => void
>T : T
>x : { y2: any; }
>a : { y2: any; }

test(b);
>test(b) : void
>test : <T>(x: { y2: any; }) => void
>b : { y2: any; }

22 changes: 22 additions & 0 deletions tests/cases/compiler/cyclicGenericTypeInstantiationInference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
function foo<T>() {
var z = foo<typeof y>();
var y: {
y2: typeof z
};
return y;
}


function bar<T>() {
var z = bar<typeof y>();
var y: {
y2: typeof z;
}
return y;
}

var a = foo<number>();
var b = bar<number>();

function test<T>(x: typeof a): void { }
test(b);