Skip to content

TSC watch / Out of memory errors with recursive template literal types #47419

Closed as not planned
@silviogutierrez

Description

@silviogutierrez

Bug Report

When using watch and template literal recursion, TSC tends to run out of memory. How soon depends on the platform and memory size, but on a powerful Silicon Mac , it takes 2-3 modifications to the code base. On a memory-limited Ubuntu VM, a single save after the initial compilation will error out.

🔎 Search Terms

watch
recursion
template literal types
memory
heap

🕗 Version & Regression Information

Tested with 4.4, 4.5, and nightly. Happens in all, consistently.

Tested on MacOS Silicon and Ubuntu.

Please keep and fill in the line that best applies:

  • This is a crash

⏯ Playground Link

This uses tsc watch, so a link to the playground is not possible. I provide sample code below.

💻 Code

https://github.com/silviogutierrez/tsc-recursion-bug

Using node 14 and yarn.

To run into the issue:

  1. git clone https://github.com/silviogutierrez/tsc-recursion-bug.git
  2. yarn tsc --noEmit --watch
  3. Note the initial compilation is very fast.
  4. Open an editor and materially change client/routes.tsx. Change the name, etc.
  5. Further compilations are slow, and eventually you'll get a massive out of memory error.

🙁 Actual behavior

Out of memory error with a long stack trace. Very little info on why.

🙂 Expected behavior

Quick compilation that takes about the same time as the initial compilation. Not only is it much slower when it does work, but it crashes eventually.

More background

The issue is caused by ExtractRouteParams in monoroutes.tsx. If you replace the definition with:

type ExtractRouteParams<T> = any

The problem immediately goes away. Obviously this isn't what we want. You can review routes.tsx to see the intent of this type. That is, to strongly type params between string and numbers.

Moreover, the problem is related to route being a recursive function. See routes.tsx for an example of how it's used to create sub routes that share paths.

Other things that "fix" the error:

  • Eliminate innerRoute from monoroutes.tsx, limiting routes to a single level.
  • Make ExtractRouteParams less powerful. Make it only parse params instead of string/number. Like so:
type ExtractRouteParams<T> = string extends T
    ? Record<string, string | number>
    : T extends `${infer _Start}<${infer Param}>/${infer Rest}`
    ? ExtractRouteParams<_Start> & {[k in Param]: string} & ExtractRouteParams<Rest>
    : T extends `${infer _Start}<${infer Param}>`
    ? {[k in Param]: string}
    : {};

Happy to provide more context. Normally I would say this is a real edge case, but the fact that it only affects watch functionality and not the initial compile leads me to believe it needs to be addressed. Thanks for all the work on typescript!

Metadata

Metadata

Assignees

Labels

Fix AvailableA PR has been opened for this issueNeeds InvestigationThis issue needs a team member to investigate its status.RescheduledThis issue was previously scheduled to an earlier milestone

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions