TypeScript: Performance regression from #48044

Bug Report

It seems like a fairly significant performance regression was introduced in this https://github.com/microsoft/TypeScript/pull/48044, which was shipped in TS 4.7. In TS 4.6, it takes ~30 seconds to run yarn tsc in our repo, and the reported strict subtype cache size is 24_011. In TS 4.7, it takes 75+ seconds to run yarn tsc (~2.5x longer than previous), and the reported strict subtype cache size is 122_778 (~5x larger than previous).

I verified that it was that specific PR that regressed the performance by taking a locally-installed 4.6.4 and applying the changes from that PR directly.

Another symptom is that several types (mostly spreads of objects into others) are now reporting as “Expression produces a union type that is too complex to represent.”

🔎 Search Terms

Expression produces a union type that is too complex to represent, strict subtype cache size

🕗 Version & Regression Information

Version Time Strict Subtype Cache Size
4.6.4 33s 24011
4.7.3 75s 122778
4.9.4 77s 122869
5.0.0-dev.20230120 78s 122869

💻 Code

I haven’t been able to narrow down a minimal reproduction of this issue, but if someone points me in the right direction I’m happy to work on it.

🙁 Actual behavior

Running yarn tsc --noEmit --extendedDiagnostics takes ~75 seconds in our repo, with a Strict subtype cache size of 122778.

🙂 Expected behavior

Running yarn tsc --noEmit --extendedDiagnostics takes ~30 seconds in our repo, with a Strict subtype cache size of 24011.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 1
  • Comments: 45 (22 by maintainers)

Most upvoted comments

That might be enough, actually. By modifiying it a bit to try and make it worse (but not enough to make it go to string), I can get the test to take a good 5-6 seconds on my machine, wheras in v4.6, it takes under a second.

type R = `${number}a` & {
    _thing: true;
};

type _S = "1" | "2" | "3" | "4" | "5" | "6";

type S = `${_S}${_S}${_S}`;


type T = R | S;
type X = `${T} ${T}`;

export type Props = Partial<{
    x: X;
}>;

const a1: Props = {};
const a2: Props = {};

const b = { ...a1, ...a2 };

export { b };

One last to test, for now, which is #53406 now that main has #53413, so we can test both together: https://github.com/microsoft/TypeScript/pull/53406#issuecomment-1478631677

#53413 will show up in a bit; that’s the one I’m actually going to merge first as I think it’s most of the problem with this perf problem as it addresses spreading. I’ll be interested if you can compare the cache sizes afterwards.

After staring at this for a while, I’m at a loss for a way to speed this up; we’re now actually traversing the intersections whereas before, we weren’t. This example in particular turns into a combinatorial explosion after #48044, which I didn’t really see coming at the time.

I’m not sure what the path forward is at the moment, besides reverting that PR (or doing nothing), but I’ll keep trying.