Skip to content

Commit 619f330

Browse files
ahejlsbergtypescript-bot
authored andcommitted
Divide-and-conquer strategy for intersections of unions (#57871)
1 parent 6ea273c commit 619f330

7 files changed

+737
-0
lines changed

src/compiler/checker.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17670,6 +17670,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1767017670
removeFromEach(typeSet, TypeFlags.Null);
1767117671
result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
1767217672
}
17673+
else if (typeSet.length >= 4) {
17674+
// When we have four or more constituents, some of which are unions, we employ a "divide and conquer" strategy
17675+
// where A & B & C & D is processed as (A & B) & (C & D). Since intersections of unions often produce far smaller
17676+
// unions of intersections than the full cartesian product (due to some intersections becoming `never`), this can
17677+
// dramatically reduce the overall work.
17678+
const middle = Math.floor(typeSet.length / 2);
17679+
result = getIntersectionType([getIntersectionType(typeSet.slice(0, middle)), getIntersectionType(typeSet.slice(middle))], aliasSymbol, aliasTypeArguments);
17680+
}
1767317681
else {
1767417682
// We are attempting to construct a type of the form X & (A | B) & (C | D). Transform this into a type of
1767517683
// the form X & A & C | X & A & D | X & B & C | X & B & D. If the estimated size of the resulting union type

tests/baselines/reference/divideAndConquerIntersections.symbols

Lines changed: 361 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
//// [tests/cases/compiler/divideAndConquerIntersections.ts] ////
2+
3+
=== Performance Stats ===
4+
Assignability cache: 100 / 100 (nearest 100)
5+
Type Count: 2,800 / 2,900 (nearest 100)
6+
Instantiation count: 7,500 / 7,500 (nearest 500)
7+
Symbol count: 26,000 / 26,000 (nearest 500)
8+
9+
=== divideAndConquerIntersections.ts ===
10+
type QQ<T extends string[]> =
11+
>QQ : QQ<T>
12+
13+
& ("a" | T[0])
14+
& ("b" | T[1])
15+
& ("c" | T[2])
16+
& ("d" | T[3])
17+
& ("e" | T[4])
18+
& ("f" | T[5])
19+
& ("g" | T[6])
20+
& ("h" | T[7])
21+
& ("i" | T[8])
22+
& ("j" | T[9])
23+
& ("k" | T[10])
24+
& ("l" | T[11])
25+
& ("m" | T[12])
26+
& ("n" | T[13])
27+
& ("q" | T[14])
28+
& ("p" | T[15])
29+
& ("q" | T[16])
30+
& ("r" | T[17])
31+
& ("s" | T[18])
32+
& ("t" | T[19]);
33+
34+
// Repro from #57863
35+
36+
export interface Update {
37+
update_id: number;
38+
>update_id : number
39+
40+
message?: { message: string };
41+
>message : { message: string; } | undefined
42+
>message : string
43+
44+
edited_message?: { edited_message: string };
45+
>edited_message : { edited_message: string; } | undefined
46+
>edited_message : string
47+
48+
channel_post?: { channel_post: string };
49+
>channel_post : { channel_post: string; } | undefined
50+
>channel_post : string
51+
52+
edited_channel_post?: { edited_channel_post: string };
53+
>edited_channel_post : { edited_channel_post: string; } | undefined
54+
>edited_channel_post : string
55+
56+
message_reaction?: { message_reaction: string };
57+
>message_reaction : { message_reaction: string; } | undefined
58+
>message_reaction : string
59+
60+
message_reaction_count?: { message_reaction_count: string };
61+
>message_reaction_count : { message_reaction_count: string; } | undefined
62+
>message_reaction_count : string
63+
64+
inline_query?: { inline_query: string };
65+
>inline_query : { inline_query: string; } | undefined
66+
>inline_query : string
67+
68+
chosen_inline_result?: { chosen_inline_result: string };
69+
>chosen_inline_result : { chosen_inline_result: string; } | undefined
70+
>chosen_inline_result : string
71+
72+
callback_query?: { callback_query: string };
73+
>callback_query : { callback_query: string; } | undefined
74+
>callback_query : string
75+
76+
shipping_query?: { shipping_query: string };
77+
>shipping_query : { shipping_query: string; } | undefined
78+
>shipping_query : string
79+
80+
pre_checkout_query?: { pre_checkout_query: string };
81+
>pre_checkout_query : { pre_checkout_query: string; } | undefined
82+
>pre_checkout_query : string
83+
84+
poll?: { poll: string };
85+
>poll : { poll: string; } | undefined
86+
>poll : string
87+
88+
poll_answer?: { poll_answer: string };
89+
>poll_answer : { poll_answer: string; } | undefined
90+
>poll_answer : string
91+
92+
my_chat_member?: { my_chat_member: string };
93+
>my_chat_member : { my_chat_member: string; } | undefined
94+
>my_chat_member : string
95+
96+
chat_member?: { chat_member: string };
97+
>chat_member : { chat_member: string; } | undefined
98+
>chat_member : string
99+
100+
chat_join_request?: { chat_join_request: string };
101+
>chat_join_request : { chat_join_request: string; } | undefined
102+
>chat_join_request : string
103+
104+
chat_boost?: { chat_boost: string };
105+
>chat_boost : { chat_boost: string; } | undefined
106+
>chat_boost : string
107+
108+
removed_chat_boost?: { removed_chat_boost: string };
109+
>removed_chat_boost : { removed_chat_boost: string; } | undefined
110+
>removed_chat_boost : string
111+
}
112+
113+
type FilterFunction<U extends Update, V extends U> = (up: U) => up is V;
114+
>FilterFunction : FilterFunction<U, V>
115+
>up : U
116+
117+
export function matchFilter<U extends Update, Q extends FilterQuery>(
118+
>matchFilter : <U extends Update, Q extends "message" | "edited_message" | "channel_post" | "edited_channel_post" | "message_reaction" | "message_reaction_count" | "inline_query" | "chosen_inline_result" | "callback_query" | "shipping_query" | "pre_checkout_query" | "poll" | "poll_answer" | "my_chat_member" | "chat_member" | "chat_join_request" | "chat_boost" | "removed_chat_boost">(filter: Q | Q[]) => FilterFunction<U, Filter<U, Q>>
119+
120+
filter: Q | Q[],
121+
>filter : Q | Q[]
122+
123+
): FilterFunction<U, Filter<U, Q>> {
124+
// ^ errors out
125+
console.log("Matching", filter);
126+
>console.log("Matching", filter) : void
127+
>console.log : (...data: any[]) => void
128+
>console : Console
129+
>log : (...data: any[]) => void
130+
>"Matching" : "Matching"
131+
>filter : "message" | "edited_message" | "channel_post" | "edited_channel_post" | "message_reaction" | "message_reaction_count" | "inline_query" | "chosen_inline_result" | "callback_query" | "shipping_query" | "pre_checkout_query" | "poll" | "poll_answer" | "my_chat_member" | "chat_member" | "chat_join_request" | "chat_boost" | "removed_chat_boost" | Q[]
132+
133+
return (up: U): up is Filter<U, Q> => !!up;
134+
>(up: U): up is Filter<U, Q> => !!up : (up: U) => up is PerformQuery<U, Combine<L1Fragment<Q>, Q>>
135+
>up : U
136+
>!!up : true
137+
>!up : false
138+
>up : U
139+
}
140+
141+
/** All valid filter queries (every update key except update_id) */
142+
export type FilterQuery = keyof Omit<Update, "update_id">;
143+
>FilterQuery : "message" | "edited_message" | "channel_post" | "edited_channel_post" | "message_reaction" | "message_reaction_count" | "inline_query" | "chosen_inline_result" | "callback_query" | "shipping_query" | "pre_checkout_query" | "poll" | "poll_answer" | "my_chat_member" | "chat_member" | "chat_join_request" | "chat_boost" | "removed_chat_boost"
144+
145+
/** Narrow down an update object based on a filter query */
146+
export type Filter<U extends Update, Q extends FilterQuery> = PerformQuery<
147+
>Filter : Filter<U, Q>
148+
149+
U,
150+
RunQuery<Q>
151+
>;
152+
153+
// generate an object structure that can be intersected with updates to narrow them down
154+
type RunQuery<Q extends string> = Combine<L1Fragment<Q>, Q>;
155+
>RunQuery : RunQuery<Q>
156+
157+
// maps each part of the filter query to Record<"key", object>
158+
type L1Fragment<Q extends string> = Q extends unknown ? Record<Q, object>
159+
>L1Fragment : L1Fragment<Q>
160+
161+
: never;
162+
// define all other fields from query as keys with value `undefined`
163+
type Combine<U, K extends string> = U extends unknown
164+
>Combine : Combine<U, K>
165+
166+
? U & Partial<Record<Exclude<K, keyof U>, undefined>>
167+
: never;
168+
169+
// apply a query result by intersecting it with update,
170+
// and then using these values to override the actual update
171+
type PerformQuery<U extends Update, R extends object> = R extends unknown
172+
>PerformQuery : PerformQuery<U, R>
173+
174+
? FilteredEvent<U, Update & R>
175+
: never;
176+
177+
// narrow down an update by intersecting it with a different update
178+
type FilteredEvent<E extends Update, U extends Update> =
179+
>FilteredEvent : FilteredEvent<E, U>
180+
181+
& E
182+
& Omit<U, "update_id">;
183+
184+
type Middleware<U extends Update> = (ctx: U) => unknown | Promise<unknown>;
185+
>Middleware : Middleware<U>
186+
>ctx : U
187+
188+
class EventHub<U extends Update> {
189+
>EventHub : EventHub<U>
190+
191+
use(...middleware: Array<Middleware<U>>): EventHub<U> {
192+
>use : (...middleware: Array<Middleware<U>>) => EventHub<U>
193+
>middleware : Middleware<U>[]
194+
195+
console.log("Adding", middleware.length, "generic handlers");
196+
>console.log("Adding", middleware.length, "generic handlers") : void
197+
>console.log : (...data: any[]) => void
198+
>console : Console
199+
>log : (...data: any[]) => void
200+
>"Adding" : "Adding"
201+
>middleware.length : number
202+
>middleware : Middleware<U>[]
203+
>length : number
204+
>"generic handlers" : "generic handlers"
205+
206+
return this;
207+
>this : this
208+
}
209+
on<Q extends FilterQuery>(
210+
>on : <Q extends "message" | "edited_message" | "channel_post" | "edited_channel_post" | "message_reaction" | "message_reaction_count" | "inline_query" | "chosen_inline_result" | "callback_query" | "shipping_query" | "pre_checkout_query" | "poll" | "poll_answer" | "my_chat_member" | "chat_member" | "chat_join_request" | "chat_boost" | "removed_chat_boost">(filter: Q | Q[], ...middleware: Array<Middleware<Filter<U, Q>>>) => EventHub<Filter<U, Q>>
211+
212+
filter: Q | Q[],
213+
>filter : Q | Q[]
214+
215+
...middleware: Array<Middleware<Filter<U, Q>>>
216+
>middleware : Middleware<PerformQuery<U, Combine<L1Fragment<Q>, Q>>>[]
217+
218+
// ^ errors out
219+
): EventHub<Filter<U, Q>> {
220+
console.log("Adding", middleware.length, "handlers for", filter);
221+
>console.log("Adding", middleware.length, "handlers for", filter) : void
222+
>console.log : (...data: any[]) => void
223+
>console : Console
224+
>log : (...data: any[]) => void
225+
>"Adding" : "Adding"
226+
>middleware.length : number
227+
>middleware : Middleware<PerformQuery<U, Combine<L1Fragment<Q>, Q>>>[]
228+
>length : number
229+
>"handlers for" : "handlers for"
230+
>filter : "message" | "edited_message" | "channel_post" | "edited_channel_post" | "message_reaction" | "message_reaction_count" | "inline_query" | "chosen_inline_result" | "callback_query" | "shipping_query" | "pre_checkout_query" | "poll" | "poll_answer" | "my_chat_member" | "chat_member" | "chat_join_request" | "chat_boost" | "removed_chat_boost" | Q[]
231+
232+
return new EventHub<Filter<U, Q>>();
233+
>new EventHub<Filter<U, Q>>() : EventHub<PerformQuery<U, Combine<L1Fragment<Q>, Q>>>
234+
>EventHub : typeof EventHub
235+
}
236+
}
237+

tests/baselines/reference/normalizedIntersectionTooComplex.types

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
//// [tests/cases/compiler/normalizedIntersectionTooComplex.ts] ////
22

3+
=== Performance Stats ===
4+
Assignability cache: 300 / 300 (nearest 100)
5+
Type Count: 1,500 / 1,500 (nearest 100)
6+
Instantiation count: 500 / 500 (nearest 500)
7+
Symbol count: 25,500 / 25,500 (nearest 500)
8+
39
=== normalizedIntersectionTooComplex.ts ===
410
// Repro from #30050
511

tests/baselines/reference/styledComponentsInstantiaionLimitNotReached.types

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
//// [tests/cases/compiler/styledComponentsInstantiaionLimitNotReached.ts] ////
22

3+
<<<<<<< HEAD
4+
=======
5+
=== Performance Stats ===
6+
Identity cache: 200 / 200 (nearest 100)
7+
Assignability cache: 13,300 / 13,500 (nearest 100)
8+
Type Count: 27,300 / 27,600 (nearest 100)
9+
Instantiation count: 374,500 / 375,500 (nearest 500)
10+
Symbol count: 92,000 / 92,000 (nearest 500)
11+
12+
>>>>>>> ecb6eb2354 (Divide-and-conquer strategy for intersections of unions (#57871))
313
=== styledComponentsInstantiaionLimitNotReached.ts ===
414
/// <reference path="react16.d.ts" />
515
import * as React from "react";

tests/baselines/reference/templateLiteralTypes1.types

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
//// [tests/cases/conformance/types/literal/templateLiteralTypes1.ts] ////
22

3+
<<<<<<< HEAD
4+
=======
5+
=== Performance Stats ===
6+
Assignability cache: 900 / 900 (nearest 100)
7+
Type Count: 16,100 / 16,100 (nearest 100)
8+
Instantiation count: 3,500 / 3,500 (nearest 500)
9+
Symbol count: 27,000 / 27,500 (nearest 500)
10+
11+
>>>>>>> ecb6eb2354 (Divide-and-conquer strategy for intersections of unions (#57871))
312
=== templateLiteralTypes1.ts ===
413
// Template types example from #12754
514

0 commit comments

Comments
 (0)