Skip to content
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

"Expression produces a union type that is too complex to represent" when using interfaces with large number of params for filtering and narrowing down certain keys #57863

Closed
SecondThundeR opened this issue Mar 20, 2024 · 4 comments Β· Fixed by #57871
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue Recent Regression This is a new regression just found in the last major/minor version of TypeScript.

Comments

@SecondThundeR
Copy link

πŸ”Ž Search Terms

Expression produces a union type that is too complex to represent, 2590, ts2590

πŸ•— Version & Regression Information

  • This behavior occurs in 5.4.2 and above. On 5.3.3 it works fine

⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.4.2#code/KYDwDg9gTgLgBASwHY2FAZgQwMbDgVTABNNU4BvAKDhrgFdjTgB9BIgLjiToFsAjNAG5K1Wj2ABnCZgDmwAPydyccVNnBOEmFGQy4AX2G04wIglRFmq6XMUUTZi1ck2NcLTqR7DomtgAWmEhIwAA2zJBadsoBQSHhkTCa2roGRrSm5qbMscFhERBRSg5ZlrnxBVrJnt7pNNbqzFDAODAIEEjRKi6Nza3tSNWpPsYNck0t2G0dORB0KF1jLH1TA7PzSe4pXmm+iEihyCwAjnRoAJ5dyIchzKcXQzsjtAGFwEisB0cTEnShMF1XhJ3p8bstJH9Nh5hnU4NhMKFQnwcABrO5nKCXYrwxHI7Bo+6Yx61PYSfwIMBgXToi5dMkUqleGlErY1XbGMDNHL+YD4uYwZlY+yclgBXko-mC4ns2iQRFdOWhaXPGiK5hBCQAdzQCogiPVSC1aGVsJ4525pGc-B1xTNFoF4mtUBNe1iDuATsBgXdTpdxjdzAAVhBkBN7kV7AHg6HmuGodsSf7vcw+BBCgDscnU+m-bRmjwIAA3bIB7MR5T5oslrNpqqsmGUfQiGDnMB4ABiCH+aHb81WHQAPPgTCBUEgiBICIxUAAaOAANRHY4nBAAfHAALxwAAUDE4+AAlJv1wxEJP58JKKBILA4Og+9MkCpSAFO92oEOl+8V4QSLO4AAil+46Tm+qBQABGLnKu257OgXbgZwQEAD6AQA2gAujOlAHpwYE9g+AxDnO+Efvgc4Aau65UP6HQSHqwAAHShBAMjbgARAAsi+5JeOxc7we+B6ws0MB0FAT67mA+64fQYBnnApHEYB64buuACE6kMMITaUAA9AAVAZcAAIKInAhYImwd4IWgcCEggkg7sAxaYnJf54CiwDnCOuBgPADAeawRBHgZelXuA0DwC2baKbZEFQZucBeecEDoHAADyPDmEO07AHO7GBUwwXsaul6GcZABymBQFAECanARD1U+QTuUwcAQHwga8vAyLAkQHUtTZ772YlYURTe0Wth28WfqAy6Tr+TAUcBK6kZBFyqXAAAKaDoNAPAbZiA57ORewAErzEd5wDpRlBlSIel6XAcghFA7WtZ13VTKydBTOJeAwN6cKtQI+zgcCUymHAmrmP4bWoJOMAQFwNV1Q1QMeo1zWUDFeCXUg123atk7Ql4W0AMIQPwRwDgAMgAjO270yOIKC3auFEPfpz08JgYCTpM8NgDV8BpXAmPDeBo0XBLKPnby0BEAO7EpfxHVdT1q649NcCM8zshszAxPzd+pMJltQGmyB9BICiSDNXA8hwAr2BK7dc5fVreycCErnCE9jXAPBIRwAioQdZjUA2WEK7oHVPAy25mCTilk6w0DFkImccAAAbzEQwdHEQuc67FVM0yEykANIk-W5NJcO1srvM9s48YzvDgAZDtosIAiA6u+7ACiIDYKEdCFwO1dzil4v4JztuFyHphUT7XAuUIj3PfzYChD5mBJz5zS-P8cB8D5yAQz1qTmDDcMI-lPNh+OEs8k+dASKkmPAlnE9OcjDqrkdCFzfngVodAESPzLngXaGADpEybqOM2U4PJznOnXL2UwtoYObpOVuDtNRID2M7Uiphh7FnZuRVB7Ue7nW1sYX2m8oDlWekgNG9VsZEJfo-c+l8UBoEhm0HYd8M7w0PmYdA6A0DvACnlGBcV3zkMocbYedclr-iQQtGhqBVJ7B7sPfRmVsrG2oYVPKJVuZ4zgJxNgRBQjAE1DVYAc1kE2w0cALa24pggBkseW2bduGoW2gnBAwIBwEOag9ceKdJwUNkQACToHwVx2iPHUT2J-YA25GK5OykQexjjnGcBMrVTAN1bEFIcU45oQ4qKyXiSgJJKSF4UD2LRQ0DFmKsQ4iZApuh1b5MKTUpiDivBAwKq9NACBsBwECOOBxUAJDsWEu0vMwAxISTfmE2ETZjCDitm4ta8VrowTWTQQSiFAJwFQgBTC2FjDGFyYxIZ1TimmTKRUuxbzalKWoZRNexgGkqOaQOP5XMMmPJeHRLpLE2LsT6WYPic5XlFOaMxd4MgJlwHYvMwpSy7zQHVpctAqyoU0FEuJJ8IQGqNJgKC8FKkYJktoE2XSbtDTwH8MkpKNK4B0uaduVZlBuV8EYh0DiSwVnCFFeKpAkqehyHVlJI8akKD6FWRyrQ7geVbllRKtC7EpUFXKPkRI7EMKrN+GKrJ24VX+PIBqmVyS5XbkNcanFpqEjpgtXOIVDqnWUCAA

πŸ’» Code

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>>();
    }
}

πŸ™ Actual behavior

TypeScript compiler throws "Expression produces a union type that is too complex to represent" on Filter type instantiation with large interface

πŸ™‚ Expected behavior

TypeScript compiler shouldn't throw "Expression produces a union type that is too complex to represent" on Filter type instantiation with large interface

Additional information about the issue

On 5.3.3, such issue doesn't occur (even when we are adding more parameters to Update interface, e.g. additional 20-30 parameters)
But, on 5.4.2 it seems like limit for it is lowered down. When chat_boost and removed_chat_boost are removed, both Filter instantiations no longer throwing errors

@fatcerberus
Copy link

I assume you mean "properties" instead of "parameters"? There are no function types in the Update interface, let alone ones with large numbers of parameters.

@RyanCavanaugh
Copy link
Member

Bisects to #56004

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Mar 20, 2024
@RyanCavanaugh
Copy link
Member

@ahejlsberg seems like Not a Defect to me but wanted to double-check

@ahejlsberg
Copy link
Member

ahejlsberg commented Mar 20, 2024

Since #56004 we reason more deeply (and correctly) about conditional type constraints, which definitely is a good thing. In this particular example it apparently causes us to materialize intersection types of the form ("foo" | Exclude<Q, "foo>) & ("bar | Exclude<Q, "bar">) & .... with 18 or so union types being intersected. This comes out to 2^18 possible constituents, which is above our 100,000 limit. Of course, the final intersection will actually only have 19 constituents because all of the literal types intersect to never. But we don't realize that.

Here's a simpler repro:

// Error: Expression produces a union type that is too complex to represent.
type Foo<T extends string> =
    & ("a" | T)
    & ("b" | T)
    & ("c" | T)
    & ("d" | T)
    & ("e" | T)
    & ("f" | T)
    & ("g" | T)
    & ("h" | T)
    & ("i" | T)
    & ("j" | T)
    & ("k" | T)
    & ("l" | T)
    & ("m" | T)
    & ("n" | T)
    & ("q" | T)
    & ("p" | T)
    & ("q" | T)
    & ("r" | T);

I'll think about ways to optimize this.

@ahejlsberg ahejlsberg added Bug A bug in TypeScript Recent Regression This is a new regression just found in the last major/minor version of TypeScript. Fix Available A PR has been opened for this issue and removed Needs Investigation This issue needs a team member to investigate its status. labels Mar 21, 2024
@DanielRosenwasser DanielRosenwasser added this to the TypeScript 5.4.32 milestone Mar 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue Recent Regression This is a new regression just found in the last major/minor version of TypeScript.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants