Skip to content

Commit

Permalink
Fix getRecursionIdentity function to always return some identity (#43527
Browse files Browse the repository at this point in the history
)

* Fix getRecursionIdentity, undo changes from #43435 (but keep tests)

* Remove test that takes excessively long to run

* Accept new baselines

* Fix formatting

* Add regression tests

* Reinstate test
  • Loading branch information
ahejlsberg authored Apr 5, 2021
1 parent f621d67 commit a7a010a
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 503 deletions.
56 changes: 28 additions & 28 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19610,30 +19610,33 @@ namespace ts {
function isDeeplyNestedType(type: Type, stack: Type[], depth: number): boolean {
if (depth >= 5) {
const identity = getRecursionIdentity(type);
if (identity) {
let count = 0;
for (let i = 0; i < depth; i++) {
if (getRecursionIdentity(stack[i]) === identity) {
count++;
if (count >= 5) {
return true;
}
let count = 0;
for (let i = 0; i < depth; i++) {
if (getRecursionIdentity(stack[i]) === identity) {
count++;
if (count >= 5) {
return true;
}
}
}
}
return false;
}

// Types with constituents that could circularly reference the type have a recursion identity. The recursion
// identity is some object that is common to instantiations of the type with the same origin.
function getRecursionIdentity(type: Type): object | undefined {
// The recursion identity of a type is an object identity that is shared among multiple instantiations of the type.
// We track recursion identities in order to identify deeply nested and possibly infinite type instantiations with
// the same origin. For example, when type parameters are in scope in an object type such as { x: T }, all
// instantiations of that type have the same recursion identity. The default recursion identity is the object
// identity of the type, meaning that every type is unique. Generally, types with constituents that could circularly
// reference the type have a recursion identity that differs from the object identity.
function getRecursionIdentity(type: Type): object {
// Object and array literals are known not to contain recursive references and don't need a recursion identity.
if (type.flags & TypeFlags.Object && !isObjectOrArrayLiteralType(type)) {
if (getObjectFlags(type) && ObjectFlags.Reference && (type as TypeReference).node) {
// Deferred type references are tracked through their associated AST node. This gives us finer
// granularity than using their associated target because each manifest type reference has a
// unique AST node.
return (type as TypeReference).node;
return (type as TypeReference).node!;
}
if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) {
// We track all object types that have an associated symbol (representing the origin of the type), but
Expand All @@ -19645,6 +19648,9 @@ namespace ts {
return type.target;
}
}
if (type.flags & TypeFlags.TypeParameter) {
return type.symbol;
}
if (type.flags & TypeFlags.IndexedAccess) {
// Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P][Q] it is A
do {
Expand All @@ -19656,7 +19662,7 @@ namespace ts {
// The root object represents the origin of the conditional type
return (type as ConditionalType).root;
}
return undefined;
return type;
}

function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean {
Expand Down Expand Up @@ -19840,13 +19846,7 @@ namespace ts {
function isArrayLikeType(type: Type): boolean {
// A type is array-like if it is a reference to the global Array or global ReadonlyArray type,
// or if it is not the undefined or null type and if it is assignable to ReadonlyArray<any>
return isArrayType(type) || hasArrayOrReadonlyArrayBaseType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType);
}

function hasArrayOrReadonlyArrayBaseType(type: Type): boolean {
return !!(getObjectFlags(type) & ObjectFlags.Reference)
&& !!(getObjectFlags((type as TypeReference).target) & ObjectFlags.ClassOrInterface)
&& some(getBaseTypes((type as TypeReference).target as InterfaceType), isArrayType);
return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType);
}

function isEmptyArrayLiteralType(type: Type): boolean {
Expand Down Expand Up @@ -21083,16 +21083,16 @@ namespace ts {
// We stop inferring and report a circularity if we encounter duplicate recursion identities on both
// the source side and the target side.
const saveExpandingFlags = expandingFlags;
const sourceIdentity = getRecursionIdentity(source) || source;
const targetIdentity = getRecursionIdentity(target) || target;
if (sourceIdentity && contains(sourceStack, sourceIdentity)) expandingFlags |= ExpandingFlags.Source;
if (targetIdentity && contains(targetStack, targetIdentity)) expandingFlags |= ExpandingFlags.Target;
const sourceIdentity = getRecursionIdentity(source);
const targetIdentity = getRecursionIdentity(target);
if (contains(sourceStack, sourceIdentity)) expandingFlags |= ExpandingFlags.Source;
if (contains(targetStack, targetIdentity)) expandingFlags |= ExpandingFlags.Target;
if (expandingFlags !== ExpandingFlags.Both) {
if (sourceIdentity) (sourceStack || (sourceStack = [])).push(sourceIdentity);
if (targetIdentity) (targetStack || (targetStack = [])).push(targetIdentity);
(sourceStack || (sourceStack = [])).push(sourceIdentity);
(targetStack || (targetStack = [])).push(targetIdentity);
action(source, target);
if (targetIdentity) targetStack.pop();
if (sourceIdentity) sourceStack.pop();
targetStack.pop();
sourceStack.pop();
}
else {
inferencePriority = InferencePriority.Circularity;
Expand Down
Loading

0 comments on commit a7a010a

Please sign in to comment.