Skip to content

Cut off inference for recursive mapped types #20370

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 5 commits into from
Dec 1, 2017

Conversation

sandersn
Copy link
Member

Fixes #19951

Previously, when inferring to a self-referential (or otherwise recursive) homomorphic mapped type from a source type that also has recursive references, type inference would enter infinite recursion.

Now there is a more complex stack for mapped type inference. It mirrors the existing symbolStack but (1) includes the source type and (2) is passed through inferTypeForHomomorphicMappedType, which is actually called outside of inferTypes, and so restarts the symbolStack cache every time.

Note that it might be possible to avoid restarting inference inside inferTypeForHomomorphicMappedType and share the two caches by using a cache key. I only experimented briefly with this so far.

Previously, when inferring to a self-referential (or otherwise recursive)
homomorphic mapped type from a source type that also has recursive
references, type inference would enter infinite recursion.

Now there is a more complex stack for mapped type inference. It mirrors
the existing symbolStack but (1) includes the source type and (2) is
passed through inferTypeForHomomorphicMappedType, which is actually
called outside of inferTypes, and so restarts the symbolStack cache
every time.
From a self-referential type.
if (contains(mappedTypeStack, [source, target.symbol], ([s1, t1], [s2, t2]) => s1 === s2 && t1 === t2)) {
return;
}
(mappedTypeStack || (mappedTypeStack = [])).push([source, target.symbol]);
Copy link
Member

Choose a reason for hiding this comment

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

function getInferenceStackKey(source: Type, target: Symbol) {
    return `${source.id},${getSymbolId(target)}`
}
// ...
const cacheKey = getInferenceStackKey(source, target.symbol);
if (mappedTypeCache && mappedTypeCache.has(cacheKey)) {
    return; // future: mappedTypeCache.get(cacheKey)
}
(mappedTypeCache || (mappedTypeCache = createMap())).set(cacheKey, true); // future: replace `true` with initialized candidate type which is filled out by the below `infer` call.
const inferredType = inferTypeForHomomorphicMappedType(source, <MappedType>target, mappedTypeCache);
mappedTypeCache.delete(cacheKey);

If the mappedTypeStack values are never needed other than for set membership checking, and the ordering is irrelevant other than for removal, then for efficiency (faster contains checks, no array allocation for each member) we should use a set and not a stack, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. I was going to do that next, but I decided to get @ahejlsberg's opinion about the general approach beforehand.

@sandersn sandersn merged commit 8d7c2a2 into master Dec 1, 2017
@sandersn sandersn deleted the cutoff-inference-for-recursive-mapped-types branch December 1, 2017 23:10
@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.

2 participants