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

an emitter with 239 events breaks type check, throw ts2590 Expression produces a union type that is too complex to represent #42790

Open
shigma opened this issue Feb 13, 2021 · 3 comments
Labels
Domain: Big Unions The root cause is ultimately that big unions interact poorly with complex structures Domain: Contextual Types The issue relates to contextual types Needs Investigation This issue needs a team member to investigate its status.

Comments

@shigma
Copy link

shigma commented Feb 13, 2021

Bug Report

I wrote a framework (namely koishi) which uses a fully-typed event emitter implemented by itself. However when I create more events (totally 239), the type check just broke with an error ts2590 (Expression produces a union type that is too complex to represent). It seems that the error was thrown here:

image

I know 239 events is a lot, but I still cannot figure out how the 239 events became "100000 type checks".

Can you help prevent this error either by optimize type check performance or by improve my code? Thanks.

🔎 Search Terms

union type, interface, event emitter, ts2590

🕗 Version & Regression Information

I tried with v4.1.3, v4.1.5 & v4.2.1-rc. They all failed.

⏯ Playground Link

This playground will not work because it imports a dependency @octokit/webhooks-definitions (it's just a simple collection of github webhooks type definitions, but it is too big (over 5000 lines) to be included as I thought).

Playground link with a dependency

And the dependency code can be found here: https://unpkg.com/@octokit/[email protected]/schema.d.ts

💻 Code

interface EventMap extends SessionEventMap, WebhookEventMap {}

declare function on<K extends keyof EventMap>(name: K, listener: EventMap[K]): () => boolean

on('message', () => {})

🙁 Actual behavior

TS2590 error: Expression produces a union type that is too complex to represent.

🙂 Expected behavior

No compiler error

@shigma shigma changed the title big event emitter breaks type check, throw ts2590 Expression produces a union type that is too complex to represent an emitter with 239 events breaks type check, throw ts2590 Expression produces a union type that is too complex to represent Feb 14, 2021
@RyanCavanaugh
Copy link
Member

@ahejlsberg or @weswigham interested in looking at this?

@weswigham
Copy link
Member

weswigham commented Feb 18, 2021

I have looked at this and have a pretty good grasp of what's up at this point. First: You "only" have 239 top-level events... but each of those events may have multiple possible payload shapes. So the handler arrow function signature's contextual type looks like (payload: A1) => void | ... | (payload: A239) => void, where A1 through A239 are themselves unions of 1 or more object types. When we have a contextual union type like this, we attempt to combine those disparate signatures into a single contextual signature. To do that, we intersect all those parameter types. That's where the limiter comes into play - our simple analysis sees that based on the union+nested intersection count, there's >200,000 possible combinations in that calculated parameter type intersection. What that complexity analysis doesn't currently know, however, is that almost all of those 200,000+ possible combinations will reduce away to never due to conflicting discriminant properties (their type field).

So there's two things I've come up with that we can do to improve here, and I have implementations for both, I just don't know if we wanna do both, and, if so, together or separately.

  1. In the example given, the callback passed to on has no explicit arguments. That means we shouldn't need to actually look at the argument types of the composite signature (since there's no argument position we actually need to contextually type). We can make it so we don't eagerly pull on and normalize the constituent types with a pretty straightforward usage of and modification to our existing SymbolFlags.DeferredType machinery for deferring union/intersection property type normalization (so it also covers union/intersection parameters). That will help the example as written, but if you rewrite it so it actually has an argument, eg, on('message', arg => {}), you're right back to an expression complexity error. Still, that allows the example given to compile in ~2s in my machine without a complexity error.
  2. Given that, integrating some kind of eager reduction pass seems useful in scenarios like this (where we're probably going to hit a complexity limit if we don't simplify the types involved). I already take a similar approach in When an intersection is going to produce an expression too complex error... #42772 - with some slight modifications, the scenario given here can also be caught by that PR's check and eagerly reduced. Doing so can remove the complexity error - however performing the reduction pass isn't free. We only need to do it once in the whole program, no matter how many times on is called (because it's part of a constraint calculation on the signature of on), so the overhead is relatively fixed... but actually reducing the type just that one time adds 52s to what is otherwise a 2s compilation on my really beefy machine. This might be able to be reduced with some investigation in faster recognition/application of reduction. Anders' recent work may be relevant here.

@weswigham weswigham added Domain: Big Unions The root cause is ultimately that big unions interact poorly with complex structures Domain: Contextual Types The issue relates to contextual types Needs Investigation This issue needs a team member to investigate its status. labels Feb 18, 2021
@alesmenzel
Copy link

Hello, are there any plans to handle this particular issue? I am still seeing this error on typescript 4.5.5. Or Can you please provide a workaround like a tsconfig.json settings to disable/set higher limit for the too complex type error?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: Big Unions The root cause is ultimately that big unions interact poorly with complex structures Domain: Contextual Types The issue relates to contextual types Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

4 participants