diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ba234e4981bef..a4253006a925f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18090,15 +18090,32 @@ namespace ts { // In addition, this will also detect when an indexed access has been chained off of 5 or more times (which is essentially // the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding false positives // for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`). + // It also detects when a recursive type reference has expanded 5 or more times, eg, if the true branch of + // `type A = null extends T ? [A>] : [T]` + // has expanded into `[A>>>>>]` + // in such cases we need to terminate the expansion, and we do so here. 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 && !isObjectOrArrayLiteralType(type)) { - const symbol = type.symbol; - if (symbol) { + if (depth >= 5 && type.flags & TypeFlags.Object) { + if (!isObjectOrArrayLiteralType(type)) { + const symbol = type.symbol; + 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; + } + } + } + } + if (getObjectFlags(type) && ObjectFlags.Reference && !!(type as TypeReference).node) { + const root = (type as TypeReference).target; let count = 0; for (let i = 0; i < depth; i++) { const t = stack[i]; - if (t.flags & TypeFlags.Object && t.symbol === symbol) { + if (getObjectFlags(t) && ObjectFlags.Reference && !!(t as TypeReference).node && (t as TypeReference).target === root) { count++; if (count >= 5) return true; } @@ -19190,6 +19207,8 @@ namespace ts { let propagationType: Type; let inferencePriority = InferencePriority.MaxValue; let allowComplexConstraintInference = true; + let objectTypeComparisonDepth = 0; + const targetStack: Type[] = []; inferFromTypes(originalSource, originalTarget); function inferFromTypes(source: Type, target: Type): void { @@ -19623,15 +19642,27 @@ namespace ts { // its symbol with the instance side which would lead to false positives. const isNonConstructorObject = target.flags & TypeFlags.Object && !(getObjectFlags(target) & ObjectFlags.Anonymous && target.symbol && target.symbol.flags & SymbolFlags.Class); - const symbolOrType = isNonConstructorObject ? isTupleType(target) ? target.target : target.symbol : undefined; + const symbolOrType = getObjectFlags(target) & ObjectFlags.Reference && (target as TypeReference).node ? getNormalizedType(target, /*writing*/ false) : isNonConstructorObject ? isTupleType(target) ? target.target : target.symbol : undefined; if (symbolOrType) { if (contains(symbolOrTypeStack, symbolOrType)) { + if (getObjectFlags(target) & ObjectFlags.Reference && (target as TypeReference).node) { + // Don't set the circularity flag for re-encountered recursive type references just because we're already exploring them + return; + } + inferencePriority = InferencePriority.Circularity; + return; + } + targetStack[objectTypeComparisonDepth] = target; + objectTypeComparisonDepth++; + if (isDeeplyNestedType(target, targetStack, objectTypeComparisonDepth)) { inferencePriority = InferencePriority.Circularity; + objectTypeComparisonDepth--; return; } (symbolOrTypeStack || (symbolOrTypeStack = [])).push(symbolOrType); inferFromObjectTypesWorker(source, target); symbolOrTypeStack.pop(); + objectTypeComparisonDepth--; } else { inferFromObjectTypesWorker(source, target); diff --git a/tests/baselines/reference/selfReferencingTypeReferenceInference.js b/tests/baselines/reference/selfReferencingTypeReferenceInference.js new file mode 100644 index 0000000000000..cf629fb363381 --- /dev/null +++ b/tests/baselines/reference/selfReferencingTypeReferenceInference.js @@ -0,0 +1,23 @@ +//// [selfReferencingTypeReferenceInference.ts] +interface Box { + __: T +} + +type Recursive = + | T + | Box> + +type InferRecursive = T extends Recursive ? R : "never!" + +// the type we are testing with +type t1 = Box> + +type t2 = InferRecursive +type t3 = InferRecursive>> // write t1 explicitly + + // Why is t2 and t3 different?? + // They have same input type! + +//// [selfReferencingTypeReferenceInference.js] +// Why is t2 and t3 different?? +// They have same input type! diff --git a/tests/baselines/reference/selfReferencingTypeReferenceInference.symbols b/tests/baselines/reference/selfReferencingTypeReferenceInference.symbols new file mode 100644 index 0000000000000..4058c21cbb817 --- /dev/null +++ b/tests/baselines/reference/selfReferencingTypeReferenceInference.symbols @@ -0,0 +1,49 @@ +=== tests/cases/compiler/selfReferencingTypeReferenceInference.ts === +interface Box { +>Box : Symbol(Box, Decl(selfReferencingTypeReferenceInference.ts, 0, 0)) +>T : Symbol(T, Decl(selfReferencingTypeReferenceInference.ts, 0, 14)) + + __: T +>__ : Symbol(Box.__, Decl(selfReferencingTypeReferenceInference.ts, 0, 18)) +>T : Symbol(T, Decl(selfReferencingTypeReferenceInference.ts, 0, 14)) +} + +type Recursive = +>Recursive : Symbol(Recursive, Decl(selfReferencingTypeReferenceInference.ts, 2, 1)) +>T : Symbol(T, Decl(selfReferencingTypeReferenceInference.ts, 4, 15)) + + | T +>T : Symbol(T, Decl(selfReferencingTypeReferenceInference.ts, 4, 15)) + + | Box> +>Box : Symbol(Box, Decl(selfReferencingTypeReferenceInference.ts, 0, 0)) +>Recursive : Symbol(Recursive, Decl(selfReferencingTypeReferenceInference.ts, 2, 1)) +>T : Symbol(T, Decl(selfReferencingTypeReferenceInference.ts, 4, 15)) + +type InferRecursive = T extends Recursive ? R : "never!" +>InferRecursive : Symbol(InferRecursive, Decl(selfReferencingTypeReferenceInference.ts, 6, 23)) +>T : Symbol(T, Decl(selfReferencingTypeReferenceInference.ts, 8, 20)) +>T : Symbol(T, Decl(selfReferencingTypeReferenceInference.ts, 8, 20)) +>Recursive : Symbol(Recursive, Decl(selfReferencingTypeReferenceInference.ts, 2, 1)) +>R : Symbol(R, Decl(selfReferencingTypeReferenceInference.ts, 8, 50)) +>R : Symbol(R, Decl(selfReferencingTypeReferenceInference.ts, 8, 50)) + +// the type we are testing with +type t1 = Box> +>t1 : Symbol(t1, Decl(selfReferencingTypeReferenceInference.ts, 8, 68)) +>Box : Symbol(Box, Decl(selfReferencingTypeReferenceInference.ts, 0, 0)) +>Box : Symbol(Box, Decl(selfReferencingTypeReferenceInference.ts, 0, 0)) + +type t2 = InferRecursive +>t2 : Symbol(t2, Decl(selfReferencingTypeReferenceInference.ts, 11, 45)) +>InferRecursive : Symbol(InferRecursive, Decl(selfReferencingTypeReferenceInference.ts, 6, 23)) +>t1 : Symbol(t1, Decl(selfReferencingTypeReferenceInference.ts, 8, 68)) + +type t3 = InferRecursive>> // write t1 explicitly +>t3 : Symbol(t3, Decl(selfReferencingTypeReferenceInference.ts, 13, 28)) +>InferRecursive : Symbol(InferRecursive, Decl(selfReferencingTypeReferenceInference.ts, 6, 23)) +>Box : Symbol(Box, Decl(selfReferencingTypeReferenceInference.ts, 0, 0)) +>Box : Symbol(Box, Decl(selfReferencingTypeReferenceInference.ts, 0, 0)) + + // Why is t2 and t3 different?? + // They have same input type! diff --git a/tests/baselines/reference/selfReferencingTypeReferenceInference.types b/tests/baselines/reference/selfReferencingTypeReferenceInference.types new file mode 100644 index 0000000000000..a33b3d9ceb75f --- /dev/null +++ b/tests/baselines/reference/selfReferencingTypeReferenceInference.types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/selfReferencingTypeReferenceInference.ts === +interface Box { + __: T +>__ : T +} + +type Recursive = +>Recursive : Recursive + + | T + | Box> + +type InferRecursive = T extends Recursive ? R : "never!" +>InferRecursive : InferRecursive + +// the type we are testing with +type t1 = Box> +>t1 : t1 + +type t2 = InferRecursive +>t2 : string | number | boolean + +type t3 = InferRecursive>> // write t1 explicitly +>t3 : string | number | boolean + + // Why is t2 and t3 different?? + // They have same input type! diff --git a/tests/cases/compiler/selfReferencingTypeReferenceInference.ts b/tests/cases/compiler/selfReferencingTypeReferenceInference.ts new file mode 100644 index 0000000000000..80aa7805f08a9 --- /dev/null +++ b/tests/cases/compiler/selfReferencingTypeReferenceInference.ts @@ -0,0 +1,18 @@ +interface Box { + __: T +} + +type Recursive = + | T + | Box> + +type InferRecursive = T extends Recursive ? R : "never!" + +// the type we are testing with +type t1 = Box> + +type t2 = InferRecursive +type t3 = InferRecursive>> // write t1 explicitly + + // Why is t2 and t3 different?? + // They have same input type! \ No newline at end of file