Divide-and-conquer strategy for intersections of unions (#57871)

This commit is contained in:
Anders Hejlsberg 2024-03-21 10:18:50 -07:00 committed by GitHub
parent 03c4b35255
commit ecb6eb2354
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 720 additions and 2 deletions

View File

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

View File

@ -0,0 +1,361 @@
//// [tests/cases/compiler/divideAndConquerIntersections.ts] ////
=== divideAndConquerIntersections.ts ===
type QQ<T extends string[]> =
>QQ : Symbol(QQ, Decl(divideAndConquerIntersections.ts, 0, 0))
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("a" | T[0])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("b" | T[1])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("c" | T[2])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("d" | T[3])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("e" | T[4])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("f" | T[5])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("g" | T[6])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("h" | T[7])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("i" | T[8])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("j" | T[9])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("k" | T[10])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("l" | T[11])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("m" | T[12])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("n" | T[13])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("q" | T[14])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("p" | T[15])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("q" | T[16])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("r" | T[17])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("s" | T[18])
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
& ("t" | T[19]);
>T : Symbol(T, Decl(divideAndConquerIntersections.ts, 0, 8))
// Repro from #57863
export interface Update {
>Update : Symbol(Update, Decl(divideAndConquerIntersections.ts, 20, 20))
update_id: number;
>update_id : Symbol(Update.update_id, Decl(divideAndConquerIntersections.ts, 24, 25))
message?: { message: string };
>message : Symbol(Update.message, Decl(divideAndConquerIntersections.ts, 25, 22))
>message : Symbol(message, Decl(divideAndConquerIntersections.ts, 27, 15))
edited_message?: { edited_message: string };
>edited_message : Symbol(Update.edited_message, Decl(divideAndConquerIntersections.ts, 27, 34))
>edited_message : Symbol(edited_message, Decl(divideAndConquerIntersections.ts, 28, 22))
channel_post?: { channel_post: string };
>channel_post : Symbol(Update.channel_post, Decl(divideAndConquerIntersections.ts, 28, 48))
>channel_post : Symbol(channel_post, Decl(divideAndConquerIntersections.ts, 29, 20))
edited_channel_post?: { edited_channel_post: string };
>edited_channel_post : Symbol(Update.edited_channel_post, Decl(divideAndConquerIntersections.ts, 29, 44))
>edited_channel_post : Symbol(edited_channel_post, Decl(divideAndConquerIntersections.ts, 30, 27))
message_reaction?: { message_reaction: string };
>message_reaction : Symbol(Update.message_reaction, Decl(divideAndConquerIntersections.ts, 30, 58))
>message_reaction : Symbol(message_reaction, Decl(divideAndConquerIntersections.ts, 31, 24))
message_reaction_count?: { message_reaction_count: string };
>message_reaction_count : Symbol(Update.message_reaction_count, Decl(divideAndConquerIntersections.ts, 31, 52))
>message_reaction_count : Symbol(message_reaction_count, Decl(divideAndConquerIntersections.ts, 32, 30))
inline_query?: { inline_query: string };
>inline_query : Symbol(Update.inline_query, Decl(divideAndConquerIntersections.ts, 32, 64))
>inline_query : Symbol(inline_query, Decl(divideAndConquerIntersections.ts, 33, 20))
chosen_inline_result?: { chosen_inline_result: string };
>chosen_inline_result : Symbol(Update.chosen_inline_result, Decl(divideAndConquerIntersections.ts, 33, 44))
>chosen_inline_result : Symbol(chosen_inline_result, Decl(divideAndConquerIntersections.ts, 34, 28))
callback_query?: { callback_query: string };
>callback_query : Symbol(Update.callback_query, Decl(divideAndConquerIntersections.ts, 34, 60))
>callback_query : Symbol(callback_query, Decl(divideAndConquerIntersections.ts, 35, 22))
shipping_query?: { shipping_query: string };
>shipping_query : Symbol(Update.shipping_query, Decl(divideAndConquerIntersections.ts, 35, 48))
>shipping_query : Symbol(shipping_query, Decl(divideAndConquerIntersections.ts, 36, 22))
pre_checkout_query?: { pre_checkout_query: string };
>pre_checkout_query : Symbol(Update.pre_checkout_query, Decl(divideAndConquerIntersections.ts, 36, 48))
>pre_checkout_query : Symbol(pre_checkout_query, Decl(divideAndConquerIntersections.ts, 37, 26))
poll?: { poll: string };
>poll : Symbol(Update.poll, Decl(divideAndConquerIntersections.ts, 37, 56))
>poll : Symbol(poll, Decl(divideAndConquerIntersections.ts, 38, 12))
poll_answer?: { poll_answer: string };
>poll_answer : Symbol(Update.poll_answer, Decl(divideAndConquerIntersections.ts, 38, 28))
>poll_answer : Symbol(poll_answer, Decl(divideAndConquerIntersections.ts, 39, 19))
my_chat_member?: { my_chat_member: string };
>my_chat_member : Symbol(Update.my_chat_member, Decl(divideAndConquerIntersections.ts, 39, 42))
>my_chat_member : Symbol(my_chat_member, Decl(divideAndConquerIntersections.ts, 40, 22))
chat_member?: { chat_member: string };
>chat_member : Symbol(Update.chat_member, Decl(divideAndConquerIntersections.ts, 40, 48))
>chat_member : Symbol(chat_member, Decl(divideAndConquerIntersections.ts, 41, 19))
chat_join_request?: { chat_join_request: string };
>chat_join_request : Symbol(Update.chat_join_request, Decl(divideAndConquerIntersections.ts, 41, 42))
>chat_join_request : Symbol(chat_join_request, Decl(divideAndConquerIntersections.ts, 42, 25))
chat_boost?: { chat_boost: string };
>chat_boost : Symbol(Update.chat_boost, Decl(divideAndConquerIntersections.ts, 42, 54))
>chat_boost : Symbol(chat_boost, Decl(divideAndConquerIntersections.ts, 43, 18))
removed_chat_boost?: { removed_chat_boost: string };
>removed_chat_boost : Symbol(Update.removed_chat_boost, Decl(divideAndConquerIntersections.ts, 43, 40))
>removed_chat_boost : Symbol(removed_chat_boost, Decl(divideAndConquerIntersections.ts, 44, 26))
}
type FilterFunction<U extends Update, V extends U> = (up: U) => up is V;
>FilterFunction : Symbol(FilterFunction, Decl(divideAndConquerIntersections.ts, 45, 1))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 47, 20))
>Update : Symbol(Update, Decl(divideAndConquerIntersections.ts, 20, 20))
>V : Symbol(V, Decl(divideAndConquerIntersections.ts, 47, 37))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 47, 20))
>up : Symbol(up, Decl(divideAndConquerIntersections.ts, 47, 54))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 47, 20))
>up : Symbol(up, Decl(divideAndConquerIntersections.ts, 47, 54))
>V : Symbol(V, Decl(divideAndConquerIntersections.ts, 47, 37))
export function matchFilter<U extends Update, Q extends FilterQuery>(
>matchFilter : Symbol(matchFilter, Decl(divideAndConquerIntersections.ts, 47, 72))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 49, 28))
>Update : Symbol(Update, Decl(divideAndConquerIntersections.ts, 20, 20))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 49, 45))
>FilterQuery : Symbol(FilterQuery, Decl(divideAndConquerIntersections.ts, 55, 1))
filter: Q | Q[],
>filter : Symbol(filter, Decl(divideAndConquerIntersections.ts, 49, 69))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 49, 45))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 49, 45))
): FilterFunction<U, Filter<U, Q>> {
>FilterFunction : Symbol(FilterFunction, Decl(divideAndConquerIntersections.ts, 45, 1))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 49, 28))
>Filter : Symbol(Filter, Decl(divideAndConquerIntersections.ts, 58, 58))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 49, 28))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 49, 45))
// ^ errors out
console.log("Matching", filter);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>filter : Symbol(filter, Decl(divideAndConquerIntersections.ts, 49, 69))
return (up: U): up is Filter<U, Q> => !!up;
>up : Symbol(up, Decl(divideAndConquerIntersections.ts, 54, 12))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 49, 28))
>up : Symbol(up, Decl(divideAndConquerIntersections.ts, 54, 12))
>Filter : Symbol(Filter, Decl(divideAndConquerIntersections.ts, 58, 58))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 49, 28))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 49, 45))
>up : Symbol(up, Decl(divideAndConquerIntersections.ts, 54, 12))
}
/** All valid filter queries (every update key except update_id) */
export type FilterQuery = keyof Omit<Update, "update_id">;
>FilterQuery : Symbol(FilterQuery, Decl(divideAndConquerIntersections.ts, 55, 1))
>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --))
>Update : Symbol(Update, Decl(divideAndConquerIntersections.ts, 20, 20))
/** Narrow down an update object based on a filter query */
export type Filter<U extends Update, Q extends FilterQuery> = PerformQuery<
>Filter : Symbol(Filter, Decl(divideAndConquerIntersections.ts, 58, 58))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 61, 19))
>Update : Symbol(Update, Decl(divideAndConquerIntersections.ts, 20, 20))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 61, 36))
>FilterQuery : Symbol(FilterQuery, Decl(divideAndConquerIntersections.ts, 55, 1))
>PerformQuery : Symbol(PerformQuery, Decl(divideAndConquerIntersections.ts, 75, 12))
U,
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 61, 19))
RunQuery<Q>
>RunQuery : Symbol(RunQuery, Decl(divideAndConquerIntersections.ts, 64, 2))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 61, 36))
>;
// generate an object structure that can be intersected with updates to narrow them down
type RunQuery<Q extends string> = Combine<L1Fragment<Q>, Q>;
>RunQuery : Symbol(RunQuery, Decl(divideAndConquerIntersections.ts, 64, 2))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 67, 14))
>Combine : Symbol(Combine, Decl(divideAndConquerIntersections.ts, 71, 12))
>L1Fragment : Symbol(L1Fragment, Decl(divideAndConquerIntersections.ts, 67, 60))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 67, 14))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 67, 14))
// maps each part of the filter query to Record<"key", object>
type L1Fragment<Q extends string> = Q extends unknown ? Record<Q, object>
>L1Fragment : Symbol(L1Fragment, Decl(divideAndConquerIntersections.ts, 67, 60))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 70, 16))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 70, 16))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 70, 16))
: never;
// define all other fields from query as keys with value `undefined`
type Combine<U, K extends string> = U extends unknown
>Combine : Symbol(Combine, Decl(divideAndConquerIntersections.ts, 71, 12))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 73, 13))
>K : Symbol(K, Decl(divideAndConquerIntersections.ts, 73, 15))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 73, 13))
? U & Partial<Record<Exclude<K, keyof U>, undefined>>
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 73, 13))
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
>K : Symbol(K, Decl(divideAndConquerIntersections.ts, 73, 15))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 73, 13))
: never;
// apply a query result by intersecting it with update,
// and then using these values to override the actual update
type PerformQuery<U extends Update, R extends object> = R extends unknown
>PerformQuery : Symbol(PerformQuery, Decl(divideAndConquerIntersections.ts, 75, 12))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 79, 18))
>Update : Symbol(Update, Decl(divideAndConquerIntersections.ts, 20, 20))
>R : Symbol(R, Decl(divideAndConquerIntersections.ts, 79, 35))
>R : Symbol(R, Decl(divideAndConquerIntersections.ts, 79, 35))
? FilteredEvent<U, Update & R>
>FilteredEvent : Symbol(FilteredEvent, Decl(divideAndConquerIntersections.ts, 81, 12))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 79, 18))
>Update : Symbol(Update, Decl(divideAndConquerIntersections.ts, 20, 20))
>R : Symbol(R, Decl(divideAndConquerIntersections.ts, 79, 35))
: never;
// narrow down an update by intersecting it with a different update
type FilteredEvent<E extends Update, U extends Update> =
>FilteredEvent : Symbol(FilteredEvent, Decl(divideAndConquerIntersections.ts, 81, 12))
>E : Symbol(E, Decl(divideAndConquerIntersections.ts, 84, 19))
>Update : Symbol(Update, Decl(divideAndConquerIntersections.ts, 20, 20))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 84, 36))
>Update : Symbol(Update, Decl(divideAndConquerIntersections.ts, 20, 20))
& E
>E : Symbol(E, Decl(divideAndConquerIntersections.ts, 84, 19))
& Omit<U, "update_id">;
>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 84, 36))
type Middleware<U extends Update> = (ctx: U) => unknown | Promise<unknown>;
>Middleware : Symbol(Middleware, Decl(divideAndConquerIntersections.ts, 86, 27))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 88, 16))
>Update : Symbol(Update, Decl(divideAndConquerIntersections.ts, 20, 20))
>ctx : Symbol(ctx, Decl(divideAndConquerIntersections.ts, 88, 37))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 88, 16))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --))
class EventHub<U extends Update> {
>EventHub : Symbol(EventHub, Decl(divideAndConquerIntersections.ts, 88, 75))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 89, 15))
>Update : Symbol(Update, Decl(divideAndConquerIntersections.ts, 20, 20))
use(...middleware: Array<Middleware<U>>): EventHub<U> {
>use : Symbol(EventHub.use, Decl(divideAndConquerIntersections.ts, 89, 34))
>middleware : Symbol(middleware, Decl(divideAndConquerIntersections.ts, 90, 8))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>Middleware : Symbol(Middleware, Decl(divideAndConquerIntersections.ts, 86, 27))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 89, 15))
>EventHub : Symbol(EventHub, Decl(divideAndConquerIntersections.ts, 88, 75))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 89, 15))
console.log("Adding", middleware.length, "generic handlers");
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>middleware.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
>middleware : Symbol(middleware, Decl(divideAndConquerIntersections.ts, 90, 8))
>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
return this;
>this : Symbol(EventHub, Decl(divideAndConquerIntersections.ts, 88, 75))
}
on<Q extends FilterQuery>(
>on : Symbol(EventHub.on, Decl(divideAndConquerIntersections.ts, 93, 5))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 94, 7))
>FilterQuery : Symbol(FilterQuery, Decl(divideAndConquerIntersections.ts, 55, 1))
filter: Q | Q[],
>filter : Symbol(filter, Decl(divideAndConquerIntersections.ts, 94, 30))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 94, 7))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 94, 7))
...middleware: Array<Middleware<Filter<U, Q>>>
>middleware : Symbol(middleware, Decl(divideAndConquerIntersections.ts, 95, 24))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>Middleware : Symbol(Middleware, Decl(divideAndConquerIntersections.ts, 86, 27))
>Filter : Symbol(Filter, Decl(divideAndConquerIntersections.ts, 58, 58))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 89, 15))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 94, 7))
// ^ errors out
): EventHub<Filter<U, Q>> {
>EventHub : Symbol(EventHub, Decl(divideAndConquerIntersections.ts, 88, 75))
>Filter : Symbol(Filter, Decl(divideAndConquerIntersections.ts, 58, 58))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 89, 15))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 94, 7))
console.log("Adding", middleware.length, "handlers for", filter);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>middleware.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
>middleware : Symbol(middleware, Decl(divideAndConquerIntersections.ts, 95, 24))
>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
>filter : Symbol(filter, Decl(divideAndConquerIntersections.ts, 94, 30))
return new EventHub<Filter<U, Q>>();
>EventHub : Symbol(EventHub, Decl(divideAndConquerIntersections.ts, 88, 75))
>Filter : Symbol(Filter, Decl(divideAndConquerIntersections.ts, 58, 58))
>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 89, 15))
>Q : Symbol(Q, Decl(divideAndConquerIntersections.ts, 94, 7))
}
}

View File

@ -0,0 +1,237 @@
//// [tests/cases/compiler/divideAndConquerIntersections.ts] ////
=== Performance Stats ===
Assignability cache: 100 / 100 (nearest 100)
Type Count: 2,800 / 2,900 (nearest 100)
Instantiation count: 7,500 / 7,500 (nearest 500)
Symbol count: 26,000 / 26,000 (nearest 500)
=== divideAndConquerIntersections.ts ===
type QQ<T extends string[]> =
>QQ : QQ<T>
& ("a" | T[0])
& ("b" | T[1])
& ("c" | T[2])
& ("d" | T[3])
& ("e" | T[4])
& ("f" | T[5])
& ("g" | T[6])
& ("h" | T[7])
& ("i" | T[8])
& ("j" | T[9])
& ("k" | T[10])
& ("l" | T[11])
& ("m" | T[12])
& ("n" | T[13])
& ("q" | T[14])
& ("p" | T[15])
& ("q" | T[16])
& ("r" | T[17])
& ("s" | T[18])
& ("t" | T[19]);
// Repro from #57863
export interface Update {
update_id: number;
>update_id : number
message?: { message: string };
>message : { message: string; } | undefined
>message : string
edited_message?: { edited_message: string };
>edited_message : { edited_message: string; } | undefined
>edited_message : string
channel_post?: { channel_post: string };
>channel_post : { channel_post: string; } | undefined
>channel_post : string
edited_channel_post?: { edited_channel_post: string };
>edited_channel_post : { edited_channel_post: string; } | undefined
>edited_channel_post : string
message_reaction?: { message_reaction: string };
>message_reaction : { message_reaction: string; } | undefined
>message_reaction : string
message_reaction_count?: { message_reaction_count: string };
>message_reaction_count : { message_reaction_count: string; } | undefined
>message_reaction_count : string
inline_query?: { inline_query: string };
>inline_query : { inline_query: string; } | undefined
>inline_query : string
chosen_inline_result?: { chosen_inline_result: string };
>chosen_inline_result : { chosen_inline_result: string; } | undefined
>chosen_inline_result : string
callback_query?: { callback_query: string };
>callback_query : { callback_query: string; } | undefined
>callback_query : string
shipping_query?: { shipping_query: string };
>shipping_query : { shipping_query: string; } | undefined
>shipping_query : string
pre_checkout_query?: { pre_checkout_query: string };
>pre_checkout_query : { pre_checkout_query: string; } | undefined
>pre_checkout_query : string
poll?: { poll: string };
>poll : { poll: string; } | undefined
>poll : string
poll_answer?: { poll_answer: string };
>poll_answer : { poll_answer: string; } | undefined
>poll_answer : string
my_chat_member?: { my_chat_member: string };
>my_chat_member : { my_chat_member: string; } | undefined
>my_chat_member : string
chat_member?: { chat_member: string };
>chat_member : { chat_member: string; } | undefined
>chat_member : string
chat_join_request?: { chat_join_request: string };
>chat_join_request : { chat_join_request: string; } | undefined
>chat_join_request : string
chat_boost?: { chat_boost: string };
>chat_boost : { chat_boost: string; } | undefined
>chat_boost : string
removed_chat_boost?: { removed_chat_boost: string };
>removed_chat_boost : { removed_chat_boost: string; } | undefined
>removed_chat_boost : string
}
type FilterFunction<U extends Update, V extends U> = (up: U) => up is V;
>FilterFunction : FilterFunction<U, V>
>up : U
export function matchFilter<U extends Update, Q extends FilterQuery>(
>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>>
filter: Q | Q[],
>filter : Q | Q[]
): FilterFunction<U, Filter<U, Q>> {
// ^ errors out
console.log("Matching", filter);
>console.log("Matching", filter) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>"Matching" : "Matching"
>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[]
return (up: U): up is Filter<U, Q> => !!up;
>(up: U): up is Filter<U, Q> => !!up : (up: U) => up is PerformQuery<U, Combine<L1Fragment<Q>, Q>>
>up : U
>!!up : true
>!up : false
>up : U
}
/** All valid filter queries (every update key except update_id) */
export type FilterQuery = keyof Omit<Update, "update_id">;
>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"
/** Narrow down an update object based on a filter query */
export type Filter<U extends Update, Q extends FilterQuery> = PerformQuery<
>Filter : Filter<U, Q>
U,
RunQuery<Q>
>;
// generate an object structure that can be intersected with updates to narrow them down
type RunQuery<Q extends string> = Combine<L1Fragment<Q>, Q>;
>RunQuery : RunQuery<Q>
// maps each part of the filter query to Record<"key", object>
type L1Fragment<Q extends string> = Q extends unknown ? Record<Q, object>
>L1Fragment : L1Fragment<Q>
: never;
// define all other fields from query as keys with value `undefined`
type Combine<U, K extends string> = U extends unknown
>Combine : Combine<U, K>
? U & Partial<Record<Exclude<K, keyof U>, undefined>>
: never;
// apply a query result by intersecting it with update,
// and then using these values to override the actual update
type PerformQuery<U extends Update, R extends object> = R extends unknown
>PerformQuery : PerformQuery<U, R>
? FilteredEvent<U, Update & R>
: never;
// narrow down an update by intersecting it with a different update
type FilteredEvent<E extends Update, U extends Update> =
>FilteredEvent : FilteredEvent<E, U>
& E
& Omit<U, "update_id">;
type Middleware<U extends Update> = (ctx: U) => unknown | Promise<unknown>;
>Middleware : Middleware<U>
>ctx : U
class EventHub<U extends Update> {
>EventHub : EventHub<U>
use(...middleware: Array<Middleware<U>>): EventHub<U> {
>use : (...middleware: Array<Middleware<U>>) => EventHub<U>
>middleware : Middleware<U>[]
console.log("Adding", middleware.length, "generic handlers");
>console.log("Adding", middleware.length, "generic handlers") : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>"Adding" : "Adding"
>middleware.length : number
>middleware : Middleware<U>[]
>length : number
>"generic handlers" : "generic handlers"
return this;
>this : this
}
on<Q extends FilterQuery>(
>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>>
filter: Q | Q[],
>filter : Q | Q[]
...middleware: Array<Middleware<Filter<U, Q>>>
>middleware : Middleware<PerformQuery<U, Combine<L1Fragment<Q>, Q>>>[]
// ^ errors out
): EventHub<Filter<U, Q>> {
console.log("Adding", middleware.length, "handlers for", filter);
>console.log("Adding", middleware.length, "handlers for", filter) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>"Adding" : "Adding"
>middleware.length : number
>middleware : Middleware<PerformQuery<U, Combine<L1Fragment<Q>, Q>>>[]
>length : number
>"handlers for" : "handlers for"
>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[]
return new EventHub<Filter<U, Q>>();
>new EventHub<Filter<U, Q>>() : EventHub<PerformQuery<U, Combine<L1Fragment<Q>, Q>>>
>EventHub : typeof EventHub
}
}

View File

@ -1,5 +1,11 @@
//// [tests/cases/compiler/normalizedIntersectionTooComplex.ts] ////
=== Performance Stats ===
Assignability cache: 300 / 300 (nearest 100)
Type Count: 1,500 / 1,500 (nearest 100)
Instantiation count: 500 / 500 (nearest 500)
Symbol count: 25,500 / 25,500 (nearest 500)
=== normalizedIntersectionTooComplex.ts ===
// Repro from #30050

View File

@ -3,7 +3,7 @@
=== Performance Stats ===
Identity cache: 200 / 200 (nearest 100)
Assignability cache: 13,300 / 13,500 (nearest 100)
Type Count: 27,200 / 27,600 (nearest 100)
Type Count: 27,300 / 27,600 (nearest 100)
Instantiation count: 374,500 / 375,500 (nearest 500)
Symbol count: 92,000 / 92,000 (nearest 500)

View File

@ -2,7 +2,7 @@
=== Performance Stats ===
Assignability cache: 900 / 900 (nearest 100)
Type Count: 15,000 / 15,000 (nearest 100)
Type Count: 16,100 / 16,100 (nearest 100)
Instantiation count: 3,500 / 3,500 (nearest 500)
Symbol count: 27,000 / 27,500 (nearest 500)

View File

@ -0,0 +1,106 @@
// @strict: true
// @noEmit: true
type QQ<T extends string[]> =
& ("a" | T[0])
& ("b" | T[1])
& ("c" | T[2])
& ("d" | T[3])
& ("e" | T[4])
& ("f" | T[5])
& ("g" | T[6])
& ("h" | T[7])
& ("i" | T[8])
& ("j" | T[9])
& ("k" | T[10])
& ("l" | T[11])
& ("m" | T[12])
& ("n" | T[13])
& ("q" | T[14])
& ("p" | T[15])
& ("q" | T[16])
& ("r" | T[17])
& ("s" | T[18])
& ("t" | T[19]);
// Repro from #57863
export interface Update {
update_id: number;
message?: { message: string };
edited_message?: { edited_message: string };
channel_post?: { channel_post: string };
edited_channel_post?: { edited_channel_post: string };
message_reaction?: { message_reaction: string };
message_reaction_count?: { message_reaction_count: string };
inline_query?: { inline_query: string };
chosen_inline_result?: { chosen_inline_result: string };
callback_query?: { callback_query: string };
shipping_query?: { shipping_query: string };
pre_checkout_query?: { pre_checkout_query: string };
poll?: { poll: string };
poll_answer?: { poll_answer: string };
my_chat_member?: { my_chat_member: string };
chat_member?: { chat_member: string };
chat_join_request?: { chat_join_request: string };
chat_boost?: { chat_boost: string };
removed_chat_boost?: { removed_chat_boost: string };
}
type FilterFunction<U extends Update, V extends U> = (up: U) => up is V;
export function matchFilter<U extends Update, Q extends FilterQuery>(
filter: Q | Q[],
): FilterFunction<U, Filter<U, Q>> {
// ^ errors out
console.log("Matching", filter);
return (up: U): up is Filter<U, Q> => !!up;
}
/** All valid filter queries (every update key except update_id) */
export type FilterQuery = keyof Omit<Update, "update_id">;
/** Narrow down an update object based on a filter query */
export type Filter<U extends Update, Q extends FilterQuery> = PerformQuery<
U,
RunQuery<Q>
>;
// generate an object structure that can be intersected with updates to narrow them down
type RunQuery<Q extends string> = Combine<L1Fragment<Q>, Q>;
// maps each part of the filter query to Record<"key", object>
type L1Fragment<Q extends string> = Q extends unknown ? Record<Q, object>
: never;
// define all other fields from query as keys with value `undefined`
type Combine<U, K extends string> = U extends unknown
? U & Partial<Record<Exclude<K, keyof U>, undefined>>
: never;
// apply a query result by intersecting it with update,
// and then using these values to override the actual update
type PerformQuery<U extends Update, R extends object> = R extends unknown
? FilteredEvent<U, Update & R>
: never;
// narrow down an update by intersecting it with a different update
type FilteredEvent<E extends Update, U extends Update> =
& E
& Omit<U, "update_id">;
type Middleware<U extends Update> = (ctx: U) => unknown | Promise<unknown>;
class EventHub<U extends Update> {
use(...middleware: Array<Middleware<U>>): EventHub<U> {
console.log("Adding", middleware.length, "generic handlers");
return this;
}
on<Q extends FilterQuery>(
filter: Q | Q[],
...middleware: Array<Middleware<Filter<U, Q>>>
// ^ errors out
): EventHub<Filter<U, Q>> {
console.log("Adding", middleware.length, "handlers for", filter);
return new EventHub<Filter<U, Q>>();
}
}