microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.3k stars 12.53k forks source link

tsserver and intellisense hang indefinitely on "loading intellisense status" and "loading..." when using assertion function and complex conditional type #51188

Open Xunnamius opened 2 years ago

Xunnamius commented 2 years ago

Bug Report

πŸ”Ž Search Terms

tsserver typescript langauge server loading intellisense status indefinite error forever asserts assertion function vscode conditional type

πŸ•— Version & Regression Information

⏯ Playground Link

Unfortunately, painstakingly copy-pasting all the types from the various packages into the playground doesn't allow me to reproduce the issue. So instead, I've created a minimal example repository that demonstrates the issue: https://github.com/Xunnamius/mre-typescript-issue-51188

πŸ’» Code

import { visit, SKIP, type Visitor } from 'unist-util-visit';

import type { Content as MdastContent } from 'mdast';

function assertNonNullable<T>(
  value: T,
  err: string
): asserts value is NonNullable<T> {
  if (value === null) {
    throw new Error(err);
  }
}

export function visitAndReveal<Tree extends MdastContent>(
  tree: Tree,
  visitor?: Visitor,
  reverse?: boolean
) {
  visit(
    tree,
    'hidden',
    (node, index, parent) => {
      // UNCOMMENTING THIS WILL KILL INTELLISENSE
      // assertNonNullable(
      //   index,
      //   'error while revealing hidden node: node index is missing'
      // );

      // UNCOMMENTING THIS WILL KILL INTELLISENSE
      // assertNonNullable(
      //  parent,
      //  'error while revealing hidden node: node parent is missing'
      //);

      return visitor?.(node, index, parent) ?? [SKIP, index];
    },
    reverse
  );
}

πŸ™ Actual behavior

Tsserver and intellisense in vscode (latest version) hang when using a TypeScript version >=4.8.0-beta. See video example below.

πŸ™‚ Expected behavior

Tsserver/intellisense does not hang in latest versions of vscode and TypeScript.


https://user-images.githubusercontent.com/656017/195998496-e83d5c99-dc27-4867-8bcc-aa3d6a54ff55.mp4

jakebailey commented 2 years ago

This seems to bisect to #49119.

ahejlsberg commented 2 years ago

Stumbled across this one and took a quick look. The core issue is that the types in the repo are very expensive to compute, as is evident by the painful slowness of intellisense in the example. For example, checking the visitAndReveal function in the example causes ~5.8M type instantiations in 4.7 when compiled with tsc. This shrinks to ~1.2M type instantiations in 4.8 and later. Those numbers are big warning flags that the types are too complex.

That said, we obviously shouldn't hang the language service and I'm not sure why that happens. The example compiles with tsc with or without the commented out section, and I would expect the same from the language service.

jakebailey commented 2 years ago

Very odd that we hang in 4.8+ when those versions have the fewer instantiations; I would have assumed the opposite.

Xunnamius commented 2 years ago

For example, checking the visitAndReveal function in the example causes ~5.8M type instantiations in 4.7 when compiled with tsc. This shrinks to ~1.2M type instantiations in 4.8 and later. Those numbers are big warning flags that the types are too complex.

Thanks for taking a look! I had a suspicion complexity was a problem after some other small issues with the types coming from the unist-util-visit package. For example, sometimes TS would give me "Type instantiation is excessively deep and possibly infinite" seemingly at random, then I'd restart the language server, and it would return the expected type.

cc unified team: @wooorm

ahejlsberg commented 2 years ago

For example, sometimes TS would give me "Type instantiation is excessively deep and possibly infinite" seemingly at random

That error is caused by our type instantiation governor when a single source element gives rise to >5M type instantiations. Any code that triggers this is definitely highly suspect. It looks like you were barely managing to squeeze under the limit in 4.7, so I would strongly recommend simplifying the types. In particular, the InclusiveDescendant<...> type appears expensive. I think it is trying to reduce the set of possible descendant types. I don't know how important that added precision is, but it clearly comes at a very high cost.

wooorm commented 2 years ago

A TS wizard can probably improve InclusiveDescendant. It’s quite useful though, I’d rather not yank it out if possible.

jakebailey commented 1 year ago

I've been looking at this one, and if you uncomment just the second commented-out snippet in the linked example repo, tsc runs out of memory and crashes, so this isn't just an issue with tsserver/intellisense.

It seems like this code is generating huge unions of huge intersections of huge unions of... and so on, and post-#49119 we can't handle it anymore; it spends all its time traversing these in relation checking and doing so turns into a huge combinatorial explosion trying to see if two of these huge types are related.

jakebailey commented 1 year ago

So, just for reference, with TS 4.7, the example in my previous comment (the repro with the second block uncommented) takes ~2.18s to compile.

One thing I noticed while investigating this is that the intersections produced by the code here are big, but appear to be repetitive and only differ in ordering. In #52891 I am playing around with sorting intersections.

Just sorting the intersections themselves (nothing else) allows the example to compile in about 40 seconds, which is still Not Good :tm: but better than OOMing.

~However, @DanielRosenwasser pointed out that there were a few fast paths which now would work for intersections so now #52891 fully mitigates the performance problem and then some, bringing the compile time down to 1.56s, which is 40% faster!!~

I'm going to try and finalize this change, e.g. add origin types to intersections to fix the display issues, but given the nature of this change, it's not likely that this can go into TS 5.0 given we're nearly at RC.

jakebailey commented 1 year ago

Ugh.... nevermind, I screwed up; I had commented out the slow code in my example so I was testing the repro without modification! Very embarrassing.

There's still a benefit, which is still good compared to OOMing, but, not what I was hoping. Looking for more now.

jakebailey commented 1 year ago

That being said... it's still 40% for the non-bad case, which is promising in and of itself.

wooorm commented 1 year ago

My q above still stands btw: if anyone knows of a better way to do this (https://github.com/microsoft/TypeScript/issues/51188#issuecomment-1294725244), I'd gladly use that. But I don't think the stuff I came up with is very weird, so other TS users might run into similar problems!

jakebailey commented 1 year ago

Unfortunately, I completely forgot that intersection ordering determines overload order, and therefore we can't sort intersections at all, so I'll have to go back to the drawing board on this.

jakebailey commented 1 year ago

I was looking at this again and was surprised to find that main no longer OOMs. It turns out that #53012 (which was backported to TS 5.0 just in time for the full release) stops the OOM on this code example. I can now compile the example in ~12s, which is still a lot slower than TS 4.7's ~2s, but at least it completes!