From de9096b42b052ffabbf2b46b573557e7c2eb259d Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Sun, 31 Mar 2024 17:28:39 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Pick=20PR=20#57871=20(Divide-and?= =?UTF-8?q?-conquer=20strategy=20for=20int...)=20into=20release-5.4=20(#57?= =?UTF-8?q?893)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anders Hejlsberg --- src/compiler/checker.ts | 8 + .../divideAndConquerIntersections.symbols | 361 ++++++++++++++++++ .../divideAndConquerIntersections.types | 231 +++++++++++ .../compiler/divideAndConquerIntersections.ts | 106 +++++ 4 files changed, 706 insertions(+) create mode 100644 tests/baselines/reference/divideAndConquerIntersections.symbols create mode 100644 tests/baselines/reference/divideAndConquerIntersections.types create mode 100644 tests/cases/compiler/divideAndConquerIntersections.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6858ae40b5bec..175caa9eef16e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17678,6 +17678,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 diff --git a/tests/baselines/reference/divideAndConquerIntersections.symbols b/tests/baselines/reference/divideAndConquerIntersections.symbols new file mode 100644 index 0000000000000..7bf1c7e537888 --- /dev/null +++ b/tests/baselines/reference/divideAndConquerIntersections.symbols @@ -0,0 +1,361 @@ +//// [tests/cases/compiler/divideAndConquerIntersections.ts] //// + +=== divideAndConquerIntersections.ts === +type QQ = +>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 = (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( +>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> { +>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 => !!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; +>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 = 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 +>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 = Combine, 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 unknown ? Record +>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 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, 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 = 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 +>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 = +>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; +>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --)) +>U : Symbol(U, Decl(divideAndConquerIntersections.ts, 84, 36)) + +type Middleware = (ctx: U) => unknown | Promise; +>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 { +>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>): EventHub { +>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( +>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 : 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> { +>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>(); +>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)) + } +} + diff --git a/tests/baselines/reference/divideAndConquerIntersections.types b/tests/baselines/reference/divideAndConquerIntersections.types new file mode 100644 index 0000000000000..5e1ce571a1d5a --- /dev/null +++ b/tests/baselines/reference/divideAndConquerIntersections.types @@ -0,0 +1,231 @@ +//// [tests/cases/compiler/divideAndConquerIntersections.ts] //// + +=== divideAndConquerIntersections.ts === +type QQ = +>QQ : QQ + + & ("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 = (up: U) => up is V; +>FilterFunction : FilterFunction +>up : U + +export function matchFilter( +>matchFilter : (filter: Q | Q[]) => FilterFunction> + + filter: Q | Q[], +>filter : Q | Q[] + +): FilterFunction> { + // ^ 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 => !!up; +>(up: U): up is Filter => !!up : (up: U) => up is PerformQuery, Q>> +>up : U +>!!up : true +>!up : false +>up : U +} + +/** All valid filter queries (every update key except update_id) */ +export type FilterQuery = keyof Omit; +>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 = PerformQuery< +>Filter : Filter + + U, + RunQuery +>; + +// generate an object structure that can be intersected with updates to narrow them down +type RunQuery = Combine, Q>; +>RunQuery : RunQuery + +// maps each part of the filter query to Record<"key", object> +type L1Fragment = Q extends unknown ? Record +>L1Fragment : L1Fragment + + : never; +// define all other fields from query as keys with value `undefined` +type Combine = U extends unknown +>Combine : Combine + + ? U & Partial, undefined>> + : never; + +// apply a query result by intersecting it with update, +// and then using these values to override the actual update +type PerformQuery = R extends unknown +>PerformQuery : PerformQuery + + ? FilteredEvent + : never; + +// narrow down an update by intersecting it with a different update +type FilteredEvent = +>FilteredEvent : FilteredEvent + + & E + & Omit; + +type Middleware = (ctx: U) => unknown | Promise; +>Middleware : Middleware +>ctx : U + +class EventHub { +>EventHub : EventHub + + use(...middleware: Array>): EventHub { +>use : (...middleware: Array>) => EventHub +>middleware : Middleware[] + + 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[] +>length : number +>"generic handlers" : "generic handlers" + + return this; +>this : this + } + on( +>on : (filter: Q | Q[], ...middleware: Array>>) => EventHub> + + filter: Q | Q[], +>filter : Q | Q[] + + ...middleware: Array>> +>middleware : Middleware, Q>>>[] + + // ^ errors out + ): EventHub> { + 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, 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>(); +>new EventHub>() : EventHub, Q>>> +>EventHub : typeof EventHub + } +} + diff --git a/tests/cases/compiler/divideAndConquerIntersections.ts b/tests/cases/compiler/divideAndConquerIntersections.ts new file mode 100644 index 0000000000000..c3a8e70169e4c --- /dev/null +++ b/tests/cases/compiler/divideAndConquerIntersections.ts @@ -0,0 +1,106 @@ +// @strict: true +// @noEmit: true + +type QQ = + & ("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 = (up: U) => up is V; + +export function matchFilter( + filter: Q | Q[], +): FilterFunction> { + // ^ errors out + console.log("Matching", filter); + return (up: U): up is Filter => !!up; +} + +/** All valid filter queries (every update key except update_id) */ +export type FilterQuery = keyof Omit; + +/** Narrow down an update object based on a filter query */ +export type Filter = PerformQuery< + U, + RunQuery +>; + +// generate an object structure that can be intersected with updates to narrow them down +type RunQuery = Combine, Q>; + +// maps each part of the filter query to Record<"key", object> +type L1Fragment = Q extends unknown ? Record + : never; +// define all other fields from query as keys with value `undefined` +type Combine = U extends unknown + ? U & Partial, undefined>> + : never; + +// apply a query result by intersecting it with update, +// and then using these values to override the actual update +type PerformQuery = R extends unknown + ? FilteredEvent + : never; + +// narrow down an update by intersecting it with a different update +type FilteredEvent = + & E + & Omit; + +type Middleware = (ctx: U) => unknown | Promise; +class EventHub { + use(...middleware: Array>): EventHub { + console.log("Adding", middleware.length, "generic handlers"); + return this; + } + on( + filter: Q | Q[], + ...middleware: Array>> + // ^ errors out + ): EventHub> { + console.log("Adding", middleware.length, "handlers for", filter); + return new EventHub>(); + } +}