-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Conversation
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.
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 |
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, 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
)
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.
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.
src/compiler/checker.ts
Outdated
i--; | ||
} | ||
if (intersection !== unionType.types) { | ||
types[unionIndex] = getUnionTypeFromSortedList(intersection, unionType.flags & TypeFlags.UnionOfUnitTypes); |
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.
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 { |
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.
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.
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.
Yeah, but I don't really want to mess with that now.
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 // @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. |
@weswigham Yes, I will look at adding handling for |
@mhegazy @weswigham Adding generalized support for |
sounds good to me. |
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.
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.
This PR optimizes creation of intersection types containing unions of unit types. Such types occur for example when obtaining
keyof X
whereX
is large union of object types with many properties.Fixes #23977.