-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Expand intersection reduction division strategy down to 3-member intersections #59425
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
All the way down to 3 member intersections. This way you can only encounter a complexity error for a pick of two (or more) 1000 property types, and not three 50 property types. I played around with some other heuristics, but they ended up being overly costly for how narrow they were - pattern literals throw a wrench into our usual intersection fast path for literal types, and we *could* preserve it in cases like this one where there's a single pattern literal type common to all otherwise-primitive unions in the intersection (intersections with other literals either produce never or that literal), but that all goes away once there are two or more pattern literals, since then you need to worry about myrid ways the patterns can combine (which is to say: potentially as many ways as the power set indicates), so it didn't seem worth it for the complexity. Even `react` has *two* of these pattern index signatures (aria- and data-), not one, so such a heuristic won't help the common case anyway. I think there is still *some* splitting and pre-simplification we can do among the literal members, probably, even when there are non-primitive members, but I haven't been able to nail down a sufficiently general algorithm yet. So instead this one-liner will probably suffice for now.
@typescript-bot test this |
@weswigham Here are the results of running the user tests with tsc comparing Everything looks good! |
Hey @weswigham, the results of running the DT tests are ready. Everything looks the same! |
@weswigham Here they are:
tscComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
Developer Information: |
@weswigham Here are the results of running the top 400 repos with tsc comparing Everything looks good! |
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.
Couple of questions to see if I understand what's going on at all.
c8: string; | ||
} | ||
|
||
const xyz: Pick<A | B | C, 'id'> = {id: 'id'} |
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.
the heuristic doesn't apply for A | B
, right?
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.
So this heuristic is for intersections - the way it applies here is to the key types within the mapped type. Before normalization, we construct keyof A & keyof B & keyof C
which are all unions of many things - and this lets us construct it as (keyof A & keyof B) & keyof C
instead, so when we see that keyof A & keyof B
just becomes keyof Base
(essentially), we know we can do keyof Base & keyof C
without exponential blowup exceeding our limits.
Definitely doesn't apply to two element intersections, though - there's no set of comparisons to divide up.
A notable thing to say is that we attempt to do literal reduction of the intersection before the pairwise breakdown, since we can do literal-only intersection combinations in linear time (we just look for literals common to every intersected union, since them intersected with one another is always never
), so this more complete division is only critical for keyof
types that contain pattern literal index signatures, like in this test.
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.
Seems like it's worth a try.
(The root problem of very popular libraries using using very complicated types needs to be addressed too; someday, somehow.)
So instead of 50^3 tripping a breaker, we instead at least try the 50^2 intersection first and see if it reduces any (and it does). The same error can still occur this way, but the objects need to have 1000 members instead of 50 now - a much more generous limit.
I played around with some other heuristics to find and simplify intersections of unions like this earlier (almost-primitive unions), but they ended up being overly costly for how narrow they were - pattern literals throw a wrench into our usual intersection fast path for literal types, and we could preserve it in cases like this one where there's a single pattern literal type common to all otherwise-primitive unions in the intersection (intersections of the pattern with other literals either produce never or that literal), but that all goes away once there are two or more pattern literals, since then you need to worry about myriad ways the patterns can combine (which is to say: potentially as many ways as the power set indicates), so it didn't seem worth it for the complexity. Even
react
has two of these pattern index signatures (aria- and data-), not one, so such a heuristic won't help the common case anyway. I think there is still some splitting and pre-simplification we can do among the literal members, probably, even when there are non-primitive members, but I haven't been able to nail down a sufficiently general algorithm yet.Fixes #58898