@@ -23568,22 +23568,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
23568
23568
!hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass;
23569
23569
}
23570
23570
23571
- // Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons
23572
- // for maxDepth or more occurrences or instantiations of the same type have been recorded on the given stack. The
23573
- // "sameness" of instantiations is determined by the getRecursionIdentity function. An intersection is considered
23574
- // deeply nested if any constituent of the intersection is deeply nested. It is possible, though highly unlikely, for
23575
- // the deeply nested check to be true in a situation where a chain of instantiations is not infinitely expanding.
23576
- // Effectively, we will generate a false positive when two types are structurally equal to at least maxDepth levels,
23577
- // but unequal at some level beyond that.
23578
- // In addition, this will also detect when an indexed access has been chained off of maxDepth more times (which is
23579
- // essentially the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding
23580
- // false positives for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`).
23581
- // It also detects when a recursive type reference has expanded maxDepth or more times, e.g. if the true branch of
23582
- // `type A<T> = null extends T ? [A<NonNullable<T>>] : [T]`
23583
- // has expanded into `[A<NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<T>>>>>>]`. In such cases we need
23584
- // to terminate the expansion, and we do so here.
23571
+ // Return true if the given type is deeply nested. We consider this to be the case when the given stack contains
23572
+ // maxDepth or more occurrences of types with the same recursion identity as the given type. The recursion identity
23573
+ // provides a shared identity for type instantiations that repeat in some (possibly infinite) pattern. For example,
23574
+ // in `type Deep<T> = { next: Deep<Deep<T>> }`, repeatedly referencing the `next` property leads to an infinite
23575
+ // sequence of ever deeper instantiations with the same recursion identity (in this case the symbol associated with
23576
+ // the object type literal).
23577
+ // A homomorphic mapped type is considered deeply nested if its target type is deeply nested, and an intersection is
23578
+ // considered deeply nested if any constituent of the intersection is deeply nested.
23579
+ // It is possible, though highly unlikely, for the deeply nested check to be true in a situation where a chain of
23580
+ // instantiations is not infinitely expanding. Effectively, we will generate a false positive when two types are
23581
+ // structurally equal to at least maxDepth levels, but unequal at some level beyond that.
23585
23582
function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3): boolean {
23586
23583
if (depth >= maxDepth) {
23584
+ if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) {
23585
+ type = getMappedTargetWithSymbol(type);
23586
+ }
23587
23587
if (type.flags & TypeFlags.Intersection) {
23588
23588
return some((type as IntersectionType).types, t => isDeeplyNestedType(t, stack, depth, maxDepth));
23589
23589
}
@@ -23592,7 +23592,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
23592
23592
let lastTypeId = 0;
23593
23593
for (let i = 0; i < depth; i++) {
23594
23594
const t = stack[i];
23595
- if (t.flags & TypeFlags.Intersection ? some((t as IntersectionType).types, u => getRecursionIdentity(u) === identity) : getRecursionIdentity(t) === identity ) {
23595
+ if (hasMatchingRecursionIdentity(t, identity)) {
23596
23596
// We only count occurrences with a higher type id than the previous occurrence, since higher
23597
23597
// type ids are an indicator of newer instantiations caused by recursion.
23598
23598
if (t.id >= lastTypeId) {
@@ -23608,6 +23608,32 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
23608
23608
return false;
23609
23609
}
23610
23610
23611
+ // Unwrap nested homomorphic mapped types and return the deepest target type that has a symbol. This better
23612
+ // preserves unique type identities for mapped types applied to explicitly written object literals. For example
23613
+ // in `Mapped<{ x: Mapped<{ x: Mapped<{ x: string }>}>}>`, each of the mapped type applications will have a
23614
+ // unique recursion identity (that of their target object type literal) and thus avoid appearing deeply nested.
23615
+ function getMappedTargetWithSymbol(type: Type) {
23616
+ let target;
23617
+ while (
23618
+ (getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped &&
23619
+ (target = getModifiersTypeFromMappedType(type as MappedType)) &&
23620
+ (target.symbol || target.flags & TypeFlags.Intersection && some((target as IntersectionType).types, t => !!t.symbol))
23621
+ ) {
23622
+ type = target;
23623
+ }
23624
+ return type;
23625
+ }
23626
+
23627
+ function hasMatchingRecursionIdentity(type: Type, identity: object): boolean {
23628
+ if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) {
23629
+ type = getMappedTargetWithSymbol(type);
23630
+ }
23631
+ if (type.flags & TypeFlags.Intersection) {
23632
+ return some((type as IntersectionType).types, t => hasMatchingRecursionIdentity(t, identity));
23633
+ }
23634
+ return getRecursionIdentity(type) === identity;
23635
+ }
23636
+
23611
23637
// The recursion identity of a type is an object identity that is shared among multiple instantiations of the type.
23612
23638
// We track recursion identities in order to identify deeply nested and possibly infinite type instantiations with
23613
23639
// the same origin. For example, when type parameters are in scope in an object type such as { x: T }, all
@@ -23623,28 +23649,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
23623
23649
// unique AST node.
23624
23650
return (type as TypeReference).node!;
23625
23651
}
23626
- if (type.symbol) {
23627
- // We track object types that have a symbol by that symbol (representing the origin of the type).
23628
- if (getObjectFlags(type) & ObjectFlags.Mapped) {
23629
- // When a homomorphic mapped type is applied to a type with a symbol, we use the symbol of that
23630
- // type as the recursion identity. This is a better strategy than using the symbol of the mapped
23631
- // type, which doesn't work well for recursive mapped types.
23632
- type = getMappedTargetWithSymbol(type);
23633
- }
23634
- if (!(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) {
23635
- // We exclude the static side of a class since it shares its symbol with the instance side.
23636
- return type.symbol;
23637
- }
23652
+ if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) {
23653
+ // We track object types that have a symbol by that symbol (representing the origin of the type), but
23654
+ // exclude the static side of a class since it shares its symbol with the instance side.
23655
+ return type.symbol;
23638
23656
}
23639
23657
if (isTupleType(type)) {
23640
23658
return type.target;
23641
23659
}
23642
23660
}
23643
23661
if (type.flags & TypeFlags.TypeParameter) {
23662
+ // We use the symbol of the type parameter such that all "fresh" instantiations of that type parameter
23663
+ // have the same recursion identity.
23644
23664
return type.symbol;
23645
23665
}
23646
23666
if (type.flags & TypeFlags.IndexedAccess) {
23647
- // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P][Q] it is A
23667
+ // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P1][P2][P3] it is A.
23648
23668
do {
23649
23669
type = (type as IndexedAccessType).objectType;
23650
23670
}
@@ -23658,14 +23678,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
23658
23678
return type;
23659
23679
}
23660
23680
23661
- function getMappedTargetWithSymbol(type: Type) {
23662
- let target = type;
23663
- while ((getObjectFlags(target) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped && isMappedTypeWithKeyofConstraintDeclaration(target as MappedType)) {
23664
- target = getModifiersTypeFromMappedType(target as MappedType);
23665
- }
23666
- return target.symbol ? target : type;
23667
- }
23668
-
23669
23681
function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean {
23670
23682
return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False;
23671
23683
}
0 commit comments