diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6224e37ea53f8..9165cc6d8ba5c 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,16 +3442,19 @@ 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; if (!(expandingFlags & 2) && isDeeplyNestedGeneric(target, targetStack)) expandingFlags |= 2; if (expandingFlags === 3) { - var result = Ternary.True; + var result = Ternary.Maybe; } else { var result = propertiesRelatedTo(source, target, reportErrors); @@ -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 maybeCache = maybeStack[depth]; + // If result is definitely true, copy assumptions to global cache, else copy to next level up + var destinationCache = result === Ternary.True || depth === 0 ? relation : maybeStack[depth - 1]; + for (var p in maybeCache) { + destinationCache[p] = maybeCache[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/baselines/reference/recursiveTypeComparison.js b/tests/baselines/reference/recursiveTypeComparison.js new file mode 100644 index 0000000000000..8195360de172d --- /dev/null +++ b/tests/baselines/reference/recursiveTypeComparison.js @@ -0,0 +1,21 @@ +//// [recursiveTypeComparison.ts] +// 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; + + +//// [recursiveTypeComparison.js] +// Before fix this would take an exceeding long time to complete (#1170) +var p; +var stuck = p; diff --git a/tests/baselines/reference/recursiveTypeComparison.types b/tests/baselines/reference/recursiveTypeComparison.types new file mode 100644 index 0000000000000..5504de74d851c --- /dev/null +++ b/tests/baselines/reference/recursiveTypeComparison.types @@ -0,0 +1,44 @@ +=== tests/cases/compiler/recursiveTypeComparison.ts === +// Before fix this would take an exceeding long time to complete (#1170) + +interface Observable { +>Observable : Observable +>T : T + + // This member can't be of type T, Property, or Observable + needThisOne: Observable; +>needThisOne : Observable +>Observable : Observable +>T : T + + // Add more to make it slower + expo1: Property; // 0.31 seconds in check +>expo1 : Property +>Property : Property +>T : T + + expo2: Property; // 3.11 seconds +>expo2 : Property +>Property : Property +>T : T + + expo3: Property; // 82.28 seconds +>expo3 : Property +>Property : Property +>T : T +} +interface Property extends Observable { } +>Property : Property +>T : T +>Observable : Observable +>T : T + +var p: Observable<{}>; +>p : Observable<{}> +>Observable : Observable + +var stuck: Property = p; +>stuck : Property +>Property : Property +>p : Observable<{}> + diff --git a/tests/baselines/reference/recursiveTypeComparison2.errors.txt b/tests/baselines/reference/recursiveTypeComparison2.errors.txt new file mode 100644 index 0000000000000..ce6311c008966 --- /dev/null +++ b/tests/baselines/reference/recursiveTypeComparison2.errors.txt @@ -0,0 +1,36 @@ +tests/cases/compiler/recursiveTypeComparison2.ts(13,80): error TS2304: Cannot find name 'StateValue'. + + +==== tests/cases/compiler/recursiveTypeComparison2.ts (1 errors) ==== + // 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; + ~~~~~~~~~~~~~~~~ +!!! error TS2304: Cannot find name 'StateValue'. + 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 diff --git a/tests/baselines/reference/recursiveTypeComparison2.js b/tests/baselines/reference/recursiveTypeComparison2.js new file mode 100644 index 0000000000000..5844ff3be44c3 --- /dev/null +++ b/tests/baselines/reference/recursiveTypeComparison2.js @@ -0,0 +1,35 @@ +//// [recursiveTypeComparison2.ts] +// 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(); + +//// [recursiveTypeComparison2.js] +// Before fix this would cause compiler to hang (#1170) +var stuck = new Bacon.Bus(); diff --git a/tests/cases/compiler/recursiveTypeComparison.ts b/tests/cases/compiler/recursiveTypeComparison.ts new file mode 100644 index 0000000000000..6ba96cb3aadef --- /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 0000000000000..6a781437abe56 --- /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