Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance of maybe stack in recursiveTypeRelatedTo #55224

Merged
merged 10 commits into from
Aug 3, 2023
43 changes: 28 additions & 15 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ import {
createPrinterWithRemoveCommentsNeverAsciiEscape,
createPrinterWithRemoveCommentsOmitTrailingSemicolon,
createPropertyNameNodeForIdentifierOrLiteral,
createStackSet,
createSymbolTable,
createTextWriter,
Debug,
Expand Down Expand Up @@ -962,6 +963,7 @@ import {
SourceFile,
SpreadAssignment,
SpreadElement,
StackSet,
startsWith,
Statement,
stringContains,
Expand Down Expand Up @@ -20348,10 +20350,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

let errorInfo: DiagnosticMessageChain | undefined;
let relatedInfo: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined;
let maybeKeys: string[];
let maybeKeys: StackSet<string>;
let sourceStack: Type[];
let targetStack: Type[];
let maybeCount = 0;
let sourceDepth = 0;
let targetDepth = 0;
let expandingFlags = ExpandingFlags.None;
Expand Down Expand Up @@ -21224,29 +21225,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
if (!maybeKeys) {
maybeKeys = [];
maybeKeys = createStackSet();
sourceStack = [];
targetStack = [];
}
else {
// If source and target are already being compared, consider them related with assumptions
if (maybeKeys.has(id)) {
return Ternary.Maybe;
}

// A key that starts with "*" is an indication that we have type references that reference constrained
// type parameters. For such keys we also check against the key we would have gotten if all type parameters
// were unconstrained.
const broadestEquivalentId = id.startsWith("*") ? getRelationKey(source, target, intersectionState, relation, /*ignoreConstraints*/ true) : undefined;
for (let i = 0; i < maybeCount; i++) {
// If source and target are already being compared, consider them related with assumptions
if (id === maybeKeys[i] || broadestEquivalentId && broadestEquivalentId === maybeKeys[i]) {
return Ternary.Maybe;
}
if (broadestEquivalentId && maybeKeys.has(broadestEquivalentId)) {
return Ternary.Maybe;
}

if (sourceDepth === 100 || targetDepth === 100) {
overflow = true;
return Ternary.False;
}
}
const maybeStart = maybeCount;
maybeKeys[maybeCount] = id;
maybeCount++;
const maybeStart = maybeKeys.size;
maybeKeys.push(id);
const saveExpandingFlags = expandingFlags;
if (recursionFlags & RecursionFlags.Source) {
sourceStack[sourceDepth] = source;
Expand Down Expand Up @@ -21301,18 +21304,28 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (result === Ternary.True || result === Ternary.Maybe) {
// If result is definitely true, record all maybe keys as having succeeded. Also, record Ternary.Maybe
// results as having succeeded once we reach depth 0, but never record Ternary.Unknown results.
jakebailey marked this conversation as resolved.
Show resolved Hide resolved
for (let i = maybeStart; i < maybeCount; i++) {
relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags);
while (maybeKeys.size > maybeStart) {
const id = maybeKeys.pop();
relation.set(id, RelationComparisonResult.Succeeded | propagatingVarianceFlags);
}
}
else {
while (maybeKeys.size > maybeStart) {
maybeKeys.pop();
}
}
maybeCount = maybeStart;
}
// Note: it's intentional that we don't pop in the else case;
// we leave them on the stack such that when we hit depth zero
// above, we can report all of them as successful.
}
else {
// A false result goes straight into global cache (when something is false under
// assumptions it will also be false without assumptions)
relation.set(id, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags);
maybeCount = maybeStart;
while (maybeKeys.size > maybeStart) {
maybeKeys.pop();
}
}
return result;
}
Expand Down
42 changes: 42 additions & 0 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2882,3 +2882,45 @@ export function isNodeLikeSystem(): boolean {
&& !(process as any).browser
&& typeof module === "object";
}


/** @internal */
export interface StackSet<T extends {}> {
has(value: T): boolean;
push(value: T): void;
pop(): T;
get size(): number;
}

/** @internal */
export function createStackSet<T extends {}>(): StackSet<T> {
const refs = new Map<T, number>();
jakebailey marked this conversation as resolved.
Show resolved Hide resolved
const stack: T[] = [];
let end = 0;
return {
has(value) {
return refs.has(value);
},
push(value) {
refs.set(value, (refs.get(value) ?? 0) + 1);
stack[end] = value;
end++;
},
pop() {
end--;
Debug.assertGreaterThanOrEqual(end, 0);
const value = stack[end];
const refCount = refs.get(value)! - 1;
if (refCount === 0) {
refs.delete(value);
}
else {
refs.set(value, refCount);
}
return value;
},
get size() {
return end;
},
};
}