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 #33130

Closed
mjbvz opened this issue Aug 29, 2019 · 4 comments · Fixed by #33150
Closed

Expression produces a union type that is too complex to represent #33130

mjbvz opened this issue Aug 29, 2019 · 4 comments · Fixed by #33150
Assignees
Labels
Bug A bug in TypeScript Domain: Performance Reports of unusually slow behavior

Comments

@mjbvz
Copy link
Contributor

mjbvz commented Aug 29, 2019

TypeScript Version: 3.6.2

Search Terms:

  • union type
  • partial

Repo

  1. git clone https://github.com/Microsoft/typescript-template-language-service-decorator

  2. npm install

  3. Open src/template-language-service-decorator.ts

  4. On line 44, remove the any cast on the line:

        for (const { name, wrapper } of this._wrappers) {
            (intercept[name] as any) = wrapper(languageService[name]!.bind(languageService));
        }
  5. Try compiling the project

Expected behavior:
This compiles with TS 3.4

Actual behavior:
Compile fails with TS 3.5+

src/template-language-service-decorator.ts:44:13 - error TS2590: Expression produces a union type that is too complex to represent.

44             intercept[name] = wrapper(languageService[name]!.bind(languageService));
               ~~~~~~~~~~~~~~~

The code likely needs to be rewritten here but I'm opening this issue to make sure the error is expected

Playground Link:

Related Issues:

@sandersn
Copy link
Member

This error was added intentionally, so it’s likely to be by design, but we should take a look at the code to confirm.

@sandersn sandersn added the Needs Investigation This issue needs a team member to investigate its status. label Aug 29, 2019
@sandersn sandersn added this to the TypeScript 3.7.0 milestone Aug 29, 2019
@weswigham
Copy link
Member

Righto, so name is a union of the 52 (! that API is big) keys in the language service object, while intercept is a Partial<LanguageService>, which means every property is a union of it's actual type (often a signature-containing object) and undefined. When we're assigning to this, we produce an intersection like - ((() => void) | undefined) & (((fileName: string) => DiagnosticWithLocation[]) | undefined) & (((fileName: string) => Diagnostic[]) | undefined) and so on, which we then normalize (so we distribute to lift the union to the outside). This normalization spreads the types, so you get something like (() => void) & undefined & undefined | (() => void) & ((fileName: string) => DiagnosticWithLocation[]) & undefined | (() => void) & ((fileName: string) => DiagnosticWithLocation[]) & ((fileName: string) => Diagnostic[]) and so on, enumerating every possible combination of the input types. Without any kind of simplification taken into account, for an intersection of 52 unions of 2 elements, we would need to produce 4.5e15 resulting top-level union elements. That's way too many. We calculate that number in advance, see that it's greater than our cap of 100000, and issue the error in the OP.

Now, this specific case, when we're distributing a ton of unions, all of which contain undefined, I think we could preemptively simplify to greatly reduce that number, since undefined can't be subtyped under our current rules (undefined intersected with pretty much anything else is always never). I posited as much when we originally added the limit, and it looks like we do have reason to do so. It's probably worth noting that what I'm thinking of'll only help situations where every input is a union containing undefined (and maybe null), though. :S

@weswigham weswigham added Bug A bug in TypeScript Domain: Performance Reports of unusually slow behavior and removed Needs Investigation This issue needs a team member to investigate its status. labels Aug 29, 2019
@fatcerberus
Copy link

My question: if normalization was really producing that many types, why did this work before instead of, you know, causing OoM?

@weswigham
Copy link
Member

weswigham commented Aug 30, 2019

Because the type simplifies massively upon construction - undefined and null, intersected with anything, are just never, so for every combination we made, we'd just get never, then discard that element and move on (which'd chew thru compute time but not really memory). Or that's what we would have done, had we been making the type prior to 3.5 - prior to 3.5 we were just not making the constraint correctly and unsoundly checked against a union of unions, so it never came up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Domain: Performance Reports of unusually slow behavior
Projects
None yet
5 participants