-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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 caching of anonymous types #18231
Conversation
We should get some memory numbers for large or medium projects that don't benefit from this (if there are there are projects like this) to make sure there's not too much overhead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a couple of questions below.
@@ -3469,8 +3468,6 @@ namespace ts { | |||
/* @internal */ | |||
export interface TypeMapper { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with this change, it would now be easier to read as type TypeMapper = (t: TypeParameter) => Type;
if (!typeParameters) { | ||
// The first time an anonymous type is instantiated we compute and store a list of the type | ||
// parameters that are in scope (and therefore potentially referenced). For type literals that | ||
// aren't the right hand side of a generic type alias declaration we optimize by reducing the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be "the rhs of a non-generic type alias declaration", right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we optimize for type literals, except when they are the RHS of a generic type alias. For those we want to preserve the distinct instantiations even when they don't reference the type parameters (for fidelity when displaying types).
return node.kind === SyntaxKind.ThisType || forEachChild(node, checkThis); | ||
} | ||
function checkIdentifier(node: Node): boolean { | ||
return node.kind === SyntaxKind.Identifier && isPartOfTypeNode(node) && getTypeFromTypeNode(<TypeNode>node) === tp || forEachChild(node, checkIdentifier); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This call to getTypeFromTypeNode is likely to 'pull' a lot of types that might otherwise never be instantiated themselves. It's probably nothing because, during instantiation, we'll surely be instantiating nearby types anyway, but worth thinking about nonetheless.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it is always fine to call getTypeFromTypeNode
. It will be called by the checker anyways when as we type check a program.
links.typeParameters = typeParameters; | ||
if (typeParameters.length) { | ||
links.instantiations = createMap<Type>(); | ||
links.instantiations.set(getTypeListId(typeParameters), target); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand how caching the uninstantiated type helps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The uninstantiated type is the same as an instantiation where each type parameter has itself as its type argument. No point in recomputing that instantiation, we already have it.
case SyntaxKind.TypeAliasDeclaration: | ||
case SyntaxKind.JSDocTemplateTag: | ||
case SyntaxKind.MappedType: | ||
const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this line needed? Aren't further iterations of the while loop equivalent to calling getOuterTypeParameters recursively?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, because we always return whenever any of these cases is true. Why not append to a local list, break, and keep looping instead of recurring and returning? Seems like it would be more efficient (as long as the final result isn't order-dependent, but that could be fixed by swapping the order in calls to append
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our use of the getOuterTypeParameters
function in code for classes and interfaces requires the list to be in outside-in order. Hence the recursion. We could insert new elements at the beginning of the list or reverse the list at the end instead, but neither are better solutions.
@sandersn Regarding memory usage, it varies from no change to massive savings, but I haven't seen meaningfully increased consumption anywhere. There is no significant change for the Monaco and TFS code bases, for example. |
We have recently seen a number of out-of-memory issues that are caused by runaway anonymous object type instantiations and comparisons. This PR introduces a new and much improved caching scheme for such types. The key idea is to track which type variables are in scope at the source origin of each anonymous object type and cache instantiations of the type based on the actual type arguments for those type variables. This means anonymous object type instantiation caching becomes as efficient as that for classes and interfaces, and out-of-memory issues that were previously fixed by switching from type aliases to interfaces should now be gone.
The RWC tests all pass. Only changes are some missing elaborations in error messages that result from improved sharing of identical types.
Fixes #16511.
Fixes #17036.
Fixes #17968.