diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6224e37ea5..2baa3d5b72 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3221,9 +3221,9 @@ module ts { // TYPE CHECKING - var subtypeRelation: Map = {}; - var assignableRelation: Map = {}; - var identityRelation: Map = {}; + var subtypeRelation: Map = {}; + var assignableRelation: Map = {}; + var identityRelation: Map = {}; function isTypeIdenticalTo(source: Type, target: Type): boolean { return checkTypeRelatedTo(source, target, identityRelation, /*errorNode*/ undefined); @@ -3258,7 +3258,7 @@ module ts { function checkTypeRelatedTo( source: Type, target: Type, - relation: Map, + relation: Map, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { @@ -3266,6 +3266,7 @@ module ts { var errorInfo: DiagnosticMessageChain; var sourceStack: ObjectType[]; var targetStack: ObjectType[]; + var maybeStack: Map[]; var expandingFlags: number; var depth = 0; var overflow = false; @@ -3424,12 +3425,12 @@ module ts { var id = source.id + "," + target.id; var related = relation[id]; if (related !== undefined) { - return related; + return related ? Ternary.True : Ternary.False; } if (depth > 0) { for (var i = 0; i < depth; i++) { // If source and target are already being compared, consider them related with assumptions - if (source === sourceStack[i] && target === targetStack[i]) { + if (maybeStack[i][id]) { return Ternary.Maybe; } } @@ -3441,10 +3442,13 @@ module ts { else { sourceStack = []; targetStack = []; + maybeStack = []; expandingFlags = 0; } sourceStack[depth] = source; targetStack[depth] = target; + maybeStack[depth] = {}; + maybeStack[depth][id] = true; depth++; var saveExpandingFlags = expandingFlags; if (!(expandingFlags & 1) && isDeeplyNestedGeneric(source, sourceStack)) expandingFlags |= 1; @@ -3469,9 +3473,18 @@ module ts { } expandingFlags = saveExpandingFlags; depth--; - // Only cache results that are free of assumptions - if (result !== Ternary.Maybe) { - relation[id] = result; + if (result) { + var sourceCache = maybeStack[depth]; + // If result is definitely true, copy assumptions to global cache, else copy to next level up + var targetCache = result === Ternary.True || depth === 0 ? relation : maybeStack[depth - 1]; + for (var p in sourceCache) { + targetCache[p] = sourceCache[p]; + } + } + else { + // A false result goes straight into global cache (when something is false under assumptions it + // will also be false without assumptions) + relation[id] = false; } return result; } @@ -5399,7 +5412,7 @@ module ts { return typeArgumentsAreAssignable; } - function checkApplicableSignature(node: CallLikeExpression, args: Node[], signature: Signature, relation: Map, excludeArgument: boolean[], reportErrors: boolean) { + function checkApplicableSignature(node: CallLikeExpression, args: Node[], signature: Signature, relation: Map, excludeArgument: boolean[], reportErrors: boolean) { for (var i = 0; i < args.length; i++) { var arg = args[i]; var argType: Type; @@ -5593,7 +5606,7 @@ module ts { return resolveErrorCall(node); - function chooseOverload(candidates: Signature[], relation: Map) { + function chooseOverload(candidates: Signature[], relation: Map) { for (var i = 0; i < candidates.length; i++) { if (!hasCorrectArity(node, args, candidates[i])) { continue; diff --git a/tests/cases/compiler/recursiveTypeComparison.ts b/tests/cases/compiler/recursiveTypeComparison.ts new file mode 100644 index 0000000000..6ba96cb3aa --- /dev/null +++ b/tests/cases/compiler/recursiveTypeComparison.ts @@ -0,0 +1,14 @@ +// Before fix this would take an exceeding long time to complete (#1170) + +interface Observable { + // This member can't be of type T, Property, or Observable + needThisOne: Observable; + // Add more to make it slower + expo1: Property; // 0.31 seconds in check + expo2: Property; // 3.11 seconds + expo3: Property; // 82.28 seconds +} +interface Property extends Observable { } + +var p: Observable<{}>; +var stuck: Property = p; diff --git a/tests/cases/compiler/recursiveTypeComparison2.ts b/tests/cases/compiler/recursiveTypeComparison2.ts new file mode 100644 index 0000000000..6a781437ab --- /dev/null +++ b/tests/cases/compiler/recursiveTypeComparison2.ts @@ -0,0 +1,30 @@ +// Before fix this would cause compiler to hang (#1170) + +declare module Bacon { + interface Event { + } + interface Error extends Event { + } + interface Observable { + zip(other: EventStream, f: (a: T, b: U) => V): EventStream; + slidingWindow(max: number, min?: number): Property; + log(): Observable; + combine(other: Observable, f: (a: T, b: U) => V): Property; + withStateMachine(initState: U, f: (state: U, event: Event) => StateValue): EventStream; + decode(mapping: Object): Property; + awaiting(other: Observable): Property; + endOnError(f?: (value: T) => boolean): Observable; + withHandler(f: (event: Event) => any): Observable; + name(name: string): Observable; + withDescription(...args: any[]): Observable; + } + interface Property extends Observable { + } + interface EventStream extends Observable { + } + interface Bus extends EventStream { + } + var Bus: new () => Bus; +} + +var stuck: Bacon.Bus = new Bacon.Bus(); \ No newline at end of file