Skip to content

Optimize intersections of unions of unit types #24137

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
May 17, 2018
Merged

Optimize intersections of unions of unit types #24137

merged 4 commits into from
May 17, 2018

Conversation

ahejlsberg
Copy link
Member

This PR optimizes creation of intersection types containing unions of unit types. Such types occur for example when obtaining keyof X where X is large union of object types with many properties.

Fixes #23977.

Copy link
Member

@weswigham weswigham left a comment

Choose a reason for hiding this comment

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

Just some comments on what we're doing here in general; looks good.

@@ -3674,6 +3674,8 @@ namespace ts {
ContainsAnyFunctionType = 1 << 26, // Type is or contains the anyFunctionType
NonPrimitive = 1 << 27, // intrinsic object type
/* @internal */
UnionOfUnitTypes = 1 << 28, // Type is union of unit types
Copy link
Member

@weswigham weswigham May 15, 2018

Choose a reason for hiding this comment

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

With this, TypeFlags is full. We're going to need a new field for flags like this (very) soon (as I don't see any that are obviously ObjectFlags). (This and the propagating flags are effectively a union's equivalent of ObjectFlags)

Copy link
Member Author

Choose a reason for hiding this comment

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

Well, TypeFlags being full is nothing new. Seems like it has been for the last many years. I actually think it is fine to put all of the flags to good use and then work out alternate solutions as the need arises. This particular flag could easily be moved to a property local to union types, but there's no reason to do so now.

i--;
}
if (intersection !== unionType.types) {
types[unionIndex] = getUnionTypeFromSortedList(intersection, unionType.flags & TypeFlags.UnionOfUnitTypes);
Copy link
Member

Choose a reason for hiding this comment

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

It's worth a comment that we skip over getUnionType and call getUnionTypeFromSortedList directly here because we believe we've already replicated its work in a more efficient fashion.

@@ -8359,7 +8359,7 @@ namespace ts {
}

// This function assumes the constituent type list is sorted and deduplicated.
function getUnionTypeFromSortedList(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
function getUnionTypeFromSortedList(types: Type[], unionOfUnitTypes: TypeFlags, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
Copy link
Member

Choose a reason for hiding this comment

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

Possible optimization: unionOfUnitTypes should just be additionalFlags, and we should calculate getPropagatingFlagsOfTypes piece-wise while doing includes, and just pass them in (rather then making another pass over the type here to calculate them).... although I know right now to save flag space we re-purpose the propagating flags during include calculation, which could make that.... hard.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, but I don't really want to mess with that now.

@weswigham
Copy link
Member

weswigham commented May 15, 2018

Ah! before I forget! This has the same downside (incompleteness?) my PR has (which I asked about being worth doing): It doesn't handle a union containing symbol or number (or other primitives), which is potentially problematic, since keyof (the easiest way to make a huge union) can return at least number. For example:

// @strict: true
// Modified repro from #23977

declare global {
    interface ElementTagNameMap {
        [index: number]: HTMLElement
    }

    interface HTMLElement {
        [index: number]: HTMLElement;
    }
}

export function assertIsElement(node: Node | null): node is Element {
    let nodeType = node === null ? null : node.nodeType;
    return nodeType === 1;
}
  
export function assertNodeTagName<
    T extends keyof ElementTagNameMap,
    U extends ElementTagNameMap[T]>(node: Node | null, tagName: T): node is U {
    if (assertIsElement(node)) {
        const nodeTagName = node.tagName.toLowerCase();
         return nodeTagName === tagName;
    }
    return false;
}
  
export function assertNodeProperty<
    T extends keyof ElementTagNameMap,
    P extends keyof ElementTagNameMap[T],
    V extends HTMLElementTagNameMap[T][P]>(node: Node | null, tagName: T, prop: P, value: V) {
    if (assertNodeTagName(node, tagName)) {
        node[prop];
    }
}

will still happily churn through types for a long time. (I checked out your branch and double checked, it does have this problem)

It's probably worth fixing - it doesn't take too deep an object structure before this becomes problematic, and a single numeric indexer foiling the optimization is not great.

@ahejlsberg
Copy link
Member Author

@weswigham Yes, I will look at adding handling for string, number, and symbol.

@ahejlsberg
Copy link
Member Author

@mhegazy @weswigham Adding generalized support for string, number, and symbol is going to add a non-trivial amount of complexity. I'm still mulling over the best algorithm, but meanwhile I think we should merge the PR as it sits now and get the real world bug resolved.

@mhegazy
Copy link
Contributor

mhegazy commented May 17, 2018

sounds good to me.

@ahejlsberg ahejlsberg merged commit 3fc3df3 into master May 17, 2018
@ahejlsberg ahejlsberg deleted the fix23977 branch May 17, 2018 20:15
mattmccutchen added a commit to mattmccutchen/TypeScript that referenced this pull request Jul 17, 2018
Helps avoid exponential blowup for `keyof` large unions even when
`keyof` each type in the union is not a union of unit types (e.g.,
because there is an index signature or a type variable).

Remove the special handling of intersections of unions of unit types
because it's no longer needed.  This reverts the code changes of pull
request microsoft#24137 (commit 3fc3df3 with
respect to 3fc727b) but keeps the test.

Fixes microsoft#24223.
mattmccutchen added a commit to mattmccutchen/TypeScript that referenced this pull request Jul 19, 2018
Helps avoid exponential blowup for `keyof` large unions even when
`keyof` each type in the union is not a union of unit types (e.g.,
because there is an index signature or a type variable).

Remove the special handling of intersections of unions of unit types
because it's no longer needed.  This reverts the code changes of pull
request microsoft#24137 (commit 3fc3df3 with
respect to 3fc727b) but keeps the test.

Fixes microsoft#24223.
@microsoft microsoft locked and limited conversation to collaborators Jul 31, 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