Skip to content

Expand type references recursively in cache key#18207

Merged
sandersn merged 4 commits into
masterfrom
recursive-type-reference-cache
Sep 5, 2017
Merged

Expand type references recursively in cache key#18207
sandersn merged 4 commits into
masterfrom
recursive-type-reference-cache

Conversation

@sandersn
Copy link
Copy Markdown
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
Copy Markdown
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.

Comment thread src/compiler/checker.ts Outdated

function isTypeReferenceWithGenericArguments(type: Type) {
return getObjectFlags(type) & ObjectFlags.Reference && some((<TypeReference>type).typeArguments, isUnconstrainedTypeParameter);
function isTypeReferenceWithGenericArguments(type: Type): type is TypeReference {
Copy link
Copy Markdown
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
Copy Markdown
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