Skip to content

Expand type references recursively in cache key #18207

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

Merged
merged 4 commits into from
Sep 5, 2017

Conversation

sandersn
Copy link
Member

@sandersn sandersn commented Sep 1, 2017

This means that A<B<T, C<U>>> will include the keys for B and C now.

Fixes the secondary cause of #17716 — when comparing Bluebird<string> to Bluebird<string>, the compilation ends up using two versions of Bluebird. And it turns out Bluebird is a very large type, and nearly every method returns another instance of Bluebird. This causes the compiler to run out of memory and crash. With this fix, compilation finishes in 2.9 seconds compared to 0.9 seconds when package deduping work correctly, skipping the structural Bluebird <-> Bluebird comparison.

The fix works because many of Bluebird's methods return Bluebird<T[]>. So extending getTypeReferenceId to understand arrays allows caching to perform better. The intuition is that if we don't learn anything by relating A<T> ==> B<T> when already relating A<U> ==> B<U>, then we also don't learn anything by relating A<T[]> ==> B<T[]> when already relating A<U[]> ==> B<U[]>.

This technique, in fact, generalises to any depth of nested type references. that is a type argument of a type reference. So if we are already relating Foo<Bar<T, Baz<V>>> ==> Qux<Quid<T, Quam<V>>> then we will skip Foo<Bar<U, Baz<W>>> ==> Qux<Quid<U, Quam<W>>>.

This works by generating the same key in getTypeReferenceId. I add one additional case that checks if a type argument is itself a type reference with generic arguments. If so, then getTypeReferenceId calls itself recursively on that type argument.

Note that we could limit the depth of the expansion when generating the key by adding a depth parameter to getTypeReferenceId.

This means that `A<B<T, C<U>>>` will include the keys for `B` and `C`
now.
Copy link
Member

@ahejlsberg ahejlsberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved with the requested change.

@@ -9782,15 +9782,15 @@ namespace ts {
return type.flags & TypeFlags.TypeParameter && !getConstraintFromTypeParameter(<TypeParameter>type);
}

function isTypeReferenceWithGenericArguments(type: Type) {
return getObjectFlags(type) & ObjectFlags.Reference && some((<TypeReference>type).typeArguments, isUnconstrainedTypeParameter);
function isTypeReferenceWithGenericArguments(type: Type): type is TypeReference {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this is not correct. A type might be a type reference even if this function returns false.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I forgot that 'type is TypeReference' implies that type is not TypeReference if the return value is false.

I forgot that 'f(x): x is T' implies that x is *not* T if f returns
false.
@sandersn sandersn merged commit b6c708d into master Sep 5, 2017
@sandersn sandersn deleted the recursive-type-reference-cache branch September 5, 2017 18:03
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants