Skip to content

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

Merged
merged 3 commits into from
Aug 14, 2024

Conversation

weswigham
Copy link
Member

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

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 typescript-bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Jul 25, 2024
@weswigham
Copy link
Member Author

@typescript-bot test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jul 25, 2024

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
test top400 ✅ Started ✅ Results
user test this ✅ Started ✅ Results
run dt ✅ Started ✅ Results
perf test this faster ✅ Started 👀 Results

@typescript-bot
Copy link
Collaborator

@weswigham Here are the results of running the user tests with tsc comparing main and refs/pull/59425/merge:

Everything looks good!

@typescript-bot
Copy link
Collaborator

Hey @weswigham, the results of running the DT tests are ready.

Everything looks the same!

You can check the log here.

@typescript-bot
Copy link
Collaborator

@weswigham
The results of the perf run you requested are in!

Here they are:

tsc

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-Unions - node (v18.15.0, x64)
Errors 30 30 ~ ~ ~ p=1.000 n=6
Symbols 62,153 62,153 ~ ~ ~ p=1.000 n=6
Types 50,242 50,242 ~ ~ ~ p=1.000 n=6
Memory used 193,550k (± 0.99%) 193,653k (± 0.92%) ~ 192,305k 196,035k p=0.471 n=6
Parse Time 1.30s (± 0.40%) 1.30s (± 1.23%) ~ 1.27s 1.31s p=0.448 n=6
Bind Time 0.71s 0.71s ~ ~ ~ p=1.000 n=6
Check Time 9.54s (± 0.32%) 9.56s (± 0.22%) ~ 9.53s 9.58s p=0.293 n=6
Emit Time 2.73s (± 0.85%) 2.73s (± 0.65%) ~ 2.71s 2.76s p=0.742 n=6
Total Time 14.27s (± 0.30%) 14.30s (± 0.31%) ~ 14.25s 14.36s p=0.295 n=6
angular-1 - node (v18.15.0, x64)
Errors 7 7 ~ ~ ~ p=1.000 n=6
Symbols 945,532 945,532 ~ ~ ~ p=1.000 n=6
Types 409,507 409,529 +22 (+ 0.01%) ~ ~ p=0.001 n=6
Memory used 1,221,161k (± 0.00%) 1,221,139k (± 0.00%) ~ 1,221,098k 1,221,189k p=0.298 n=6
Parse Time 6.61s (± 0.54%) 6.63s (± 0.70%) ~ 6.58s 6.69s p=0.376 n=6
Bind Time 1.85s (± 0.63%) 1.85s (± 0.29%) ~ 1.85s 1.86s p=0.498 n=6
Check Time 31.16s (± 0.34%) 31.25s (± 0.68%) ~ 30.99s 31.51s p=0.423 n=6
Emit Time 15.05s (± 0.64%) 14.99s (± 0.61%) ~ 14.89s 15.12s p=0.335 n=6
Total Time 54.66s (± 0.38%) 54.73s (± 0.57%) ~ 54.31s 55.12s p=0.630 n=6
mui-docs - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 2,433,165 2,433,165 ~ ~ ~ p=1.000 n=6
Types 1,002,715 995,716 -6,999 (- 0.70%) ~ ~ p=0.001 n=6
Memory used 2,424,268k (± 0.00%) 2,292,113k (± 0.00%) 🟩-132,156k (- 5.45%) 2,292,003k 2,292,161k p=0.005 n=6
Parse Time 8.47s (± 0.22%) 8.44s (± 0.24%) ~ 8.42s 8.48s p=0.089 n=6
Bind Time 2.08s (± 0.47%) 2.08s (± 0.50%) ~ 2.07s 2.09s p=0.784 n=6
Check Time 74.44s (± 0.51%) 73.30s (± 0.35%) -1.13s (- 1.52%) 72.89s 73.52s p=0.005 n=6
Emit Time 0.27s (± 1.89%) 0.27s (± 1.50%) ~ 0.27s 0.28s p=0.595 n=6
Total Time 85.25s (± 0.43%) 84.10s (± 0.30%) -1.15s (- 1.35%) 83.69s 84.35s p=0.005 n=6
self-build-src - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,230,046 1,230,046 ~ ~ ~ p=1.000 n=6
Types 265,645 265,645 ~ ~ ~ p=1.000 n=6
Memory used 2,406,733k (± 6.04%) 2,466,600k (±11.76%) ~ 2,347,134k 3,059,488k p=0.230 n=6
Parse Time 5.01s (± 1.19%) 5.02s (± 1.16%) ~ 4.95s 5.10s p=0.936 n=6
Bind Time 1.90s (± 0.43%) 1.89s (± 0.78%) -0.01s (- 0.79%) 1.86s 1.90s p=0.045 n=6
Check Time 34.60s (± 0.66%) 34.73s (± 0.59%) ~ 34.48s 35.02s p=0.575 n=6
Emit Time 3.29s (± 0.85%) 3.26s (± 1.58%) ~ 3.17s 3.33s p=0.422 n=6
Total Time 44.82s (± 0.49%) 44.90s (± 0.50%) ~ 44.57s 45.18s p=0.575 n=6
self-build-src-public-api - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,230,046 1,230,046 ~ ~ ~ p=1.000 n=6
Types 265,645 265,645 ~ ~ ~ p=1.000 n=6
Memory used 2,421,875k (± 0.03%) 2,421,838k (± 0.03%) ~ 2,420,669k 2,422,840k p=1.000 n=6
Parse Time 7.76s (± 1.14%) 7.74s (± 0.65%) ~ 7.67s 7.81s p=0.575 n=6
Bind Time 2.50s (± 0.51%) 2.51s (± 0.39%) ~ 2.50s 2.52s p=0.254 n=6
Check Time 51.47s (± 0.54%) 51.39s (± 0.72%) ~ 50.97s 51.85s p=0.810 n=6
Emit Time 5.04s (± 3.59%) 4.98s (± 1.25%) ~ 4.90s 5.06s p=0.748 n=6
Total Time 66.81s (± 0.40%) 66.63s (± 0.59%) ~ 66.18s 67.07s p=0.575 n=6
self-compiler - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 258,471 258,471 ~ ~ ~ p=1.000 n=6
Types 105,534 105,533 -1 (- 0.00%) ~ ~ p=0.001 n=6
Memory used 429,130k (± 0.03%) 429,309k (± 0.07%) ~ 428,993k 429,795k p=0.471 n=6
Parse Time 3.36s (± 0.61%) 3.35s (± 0.16%) ~ 3.34s 3.35s p=0.228 n=6
Bind Time 1.32s (± 1.33%) 1.33s (± 1.32%) ~ 1.30s 1.35s p=0.283 n=6
Check Time 18.02s (± 0.34%) 18.00s (± 0.27%) ~ 17.95s 18.08s p=0.747 n=6
Emit Time 1.63s (± 1.43%) 1.63s (± 1.08%) ~ 1.61s 1.66s p=1.000 n=6
Total Time 24.33s (± 0.35%) 24.31s (± 0.29%) ~ 24.22s 24.43s p=0.630 n=6
ts-pre-modules - node (v18.15.0, x64)
Errors 35 35 ~ ~ ~ p=1.000 n=6
Symbols 224,931 224,931 ~ ~ ~ p=1.000 n=6
Types 94,146 94,146 ~ ~ ~ p=1.000 n=6
Memory used 370,077k (± 0.04%) 370,014k (± 0.03%) ~ 369,915k 370,254k p=0.378 n=6
Parse Time 2.76s (± 0.67%) 2.75s (± 0.96%) ~ 2.71s 2.78s p=0.871 n=6
Bind Time 1.57s (± 0.62%) 1.57s (± 1.18%) ~ 1.56s 1.61s p=0.738 n=6
Check Time 15.64s (± 0.19%) 15.63s (± 0.28%) ~ 15.56s 15.67s p=0.872 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 19.97s (± 0.19%) 19.95s (± 0.17%) ~ 19.91s 20.01s p=0.520 n=6
vscode - node (v18.15.0, x64)
Errors 11 11 ~ ~ ~ p=1.000 n=6
Symbols 2,986,390 2,986,390 ~ ~ ~ p=1.000 n=6
Types 1,027,268 1,027,277 +9 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 3,110,482k (± 0.00%) 3,110,523k (± 0.00%) ~ 3,110,449k 3,110,574k p=0.423 n=6
Parse Time 17.14s (± 0.25%) 17.18s (± 0.48%) ~ 17.08s 17.27s p=0.518 n=6
Bind Time 5.21s (± 0.21%) 5.23s (± 0.33%) ~ 5.21s 5.25s p=0.055 n=6
Check Time 96.31s (± 0.42%) 96.36s (± 0.17%) ~ 96.05s 96.52s p=0.297 n=6
Emit Time 24.87s (± 0.33%) 24.97s (± 0.81%) ~ 24.68s 25.20s p=0.471 n=6
Total Time 143.53s (± 0.27%) 143.72s (± 0.09%) ~ 143.57s 143.91s p=0.078 n=6
webpack - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 267,560 267,560 ~ ~ ~ p=1.000 n=6
Types 109,076 109,082 +6 (+ 0.01%) ~ ~ p=0.001 n=6
Memory used 412,398k (± 0.01%) 412,413k (± 0.02%) ~ 412,296k 412,565k p=0.936 n=6
Parse Time 3.83s (± 0.40%) 3.82s (± 0.32%) ~ 3.81s 3.84s p=1.000 n=6
Bind Time 1.72s (± 0.64%) 1.72s ~ ~ ~ p=0.598 n=6
Check Time 16.87s (± 0.42%) 16.82s (± 0.29%) ~ 16.75s 16.87s p=0.146 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 22.42s (± 0.32%) 22.36s (± 0.18%) ~ 22.31s 22.41s p=0.127 n=6
xstate-main - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 511,803 511,803 ~ ~ ~ p=1.000 n=6
Types 162,088 162,093 +5 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 449,334k (± 0.03%) 449,224k (± 0.07%) ~ 448,615k 449,574k p=0.810 n=6
Parse Time 3.16s (± 1.23%) 3.15s (± 1.32%) ~ 3.08s 3.20s p=0.743 n=6
Bind Time 1.16s (± 0.54%) 1.16s (± 0.89%) ~ 1.15s 1.18s p=0.654 n=6
Check Time 17.17s (± 0.16%) 17.17s (± 0.23%) ~ 17.11s 17.23s p=1.000 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 21.49s (± 0.19%) 21.48s (± 0.31%) ~ 21.38s 21.54s p=0.936 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • Compiler-Unions - node (v18.15.0, x64)
  • angular-1 - node (v18.15.0, x64)
  • mui-docs - node (v18.15.0, x64)
  • self-build-src - node (v18.15.0, x64)
  • self-build-src-public-api - node (v18.15.0, x64)
  • self-compiler - node (v18.15.0, x64)
  • ts-pre-modules - node (v18.15.0, x64)
  • vscode - node (v18.15.0, x64)
  • webpack - node (v18.15.0, x64)
  • xstate-main - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

@typescript-bot
Copy link
Collaborator

@weswigham Here are the results of running the top 400 repos with tsc comparing main and refs/pull/59425/merge:

Everything looks good!

Copy link
Member

@sandersn sandersn left a 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'}
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

@sandersn sandersn left a 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.)

@weswigham weswigham merged commit 3a43940 into microsoft:main Aug 14, 2024
32 checks passed
@sandersn sandersn removed this from PR Backlog Apr 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unable to use Pick on union with data-${string} index signature
4 participants