-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Divide-and-conquer strategy for intersections of unions #57871
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
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
55808fe
Divide-and-conquer strategy for intersections of unions
ahejlsberg 43f1d44
Accept new baselines
ahejlsberg a8e74ae
Add tests
ahejlsberg 5bace94
Properly cache outermost result
ahejlsberg a83f592
Include type alias
ahejlsberg 72352dc
Accept new baselines
ahejlsberg df41141
Integer positions only
ahejlsberg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
361 changes: 361 additions & 0 deletions
361
tests/baselines/reference/divideAndConquerIntersections.symbols
Large diffs are not rendered by default.
Oops, something went wrong.
237 changes: 237 additions & 0 deletions
237
tests/baselines/reference/divideAndConquerIntersections.types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} | ||
|
6 changes: 6 additions & 0 deletions
6
tests/baselines/reference/normalizedIntersectionTooComplex.types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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>>(); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.