Skip to content

[7.0 regression] Unbounded type relation expansion during variance computation causes OOM #64423

Open

Description

🔎 Search Terms

  • typescript 7.0.2 tsc noEmit out of memory
  • typescript-go checker Relater recursiveTypeRelatedTo memory
  • typescript 7 silent typecheck hang

🕗 Version & Regression Information

The project was using TypeScript 7.0.2 when tsc --noEmit stopped returning and consumed increasing memory. We switched the primary compiler dependency to TypeScript 6.0.3 and kept 7.0.2 installed as an npm alias so we could run both versions on the same source tree and configuration. TypeScript 6.0.3 completes successfully; 7.0.2 runs out of memory under a 6 GiB virtual-address limit.

We have not bisected TypeScript 7 releases, so we cannot identify the first affected TypeScript 7 version. The original uncapped run did not complete; the bounded failure described below is reproducible.

⏯ Playground Link

No response

💻 Code

This is a private React 19/TypeScript project with TSX application code, types, E2E tests, and Vite/Playwright configuration. Relevant tsconfig.json settings (excerpt, not a standalone reproduction):

{
  "compilerOptions": {
    "strict": true,
    "jsx": "react-jsx",
    "moduleResolution": "bundler",
    "skipLibCheck": true,
    "composite": true,
    "incremental": true,
    "noEmit": true
  }
}

The config includes src/**/*, types/**/*, and e2e/**/*, plus config files, and excludes node_modules and the local rendering dependency. The project's typecheck script is tsc --noEmit. With dependencies installed in this private project, these commands compare both compilers on the same checkout (the alias typescript7 points to typescript@7.0.2):

node_modules/typescript/bin/tsc --noEmit --pretty false --incremental false --composite false

prlimit --as=6442450944 -- node_modules/typescript7/bin/tsc --noEmit --pretty false --incremental false --composite false

prlimit --as=6442450944 -- node_modules/typescript7/bin/tsc --noEmit --noCheck --pretty false --incremental false --composite false

Both full-check commands disable incremental/composite mode to avoid build-cache effects. The prlimit cap prevents the native TypeScript 7 binary from exhausting the host's memory.

🙁 Actual behavior

TypeScript 7.0.2 produces no compiler progress, diagnostic, or error output while memory grows; the original uncapped run never returned. The developer also observed no compiler output even with verbose logging enabled. In a bounded run, the first output appears only when the Go runtime runs out of memory:

runtime: out of memory: cannot allocate 4194304-byte block (5082873856 in use)
fatal error: out of memory

With a 6 GiB virtual-address limit, the TypeScript 7 check exited with status 2 after about 36 seconds and roughly 4.8 GiB peak RSS (three runs). The same source tree completed under TypeScript 6.0.3 without diagnostics.

🙂 Expected behavior

The full TypeScript 7 typecheck should finish or report actionable diagnostics without exhausting available memory on a project that TypeScript 6 checks successfully. If it cannot finish, it should not silently consume memory without producing useful output.

Additional information about the issue

Environment: Linux x64, 8 available CPUs, 15 GiB physical memory, 8 GiB swap, Node.js v26.8.0-alpha.0.0.0. TypeScript 7.0.2 uses its native Linux x64 Go compiler.

Measurements used /usr/bin/time -f 'elapsed=%e max_rss_kb=%M exit=%x' with a 90-second timeout. The TypeScript 6 run had no address-space cap; the TypeScript 7 runs had a 6 GiB virtual-address cap.

Compiler / mode Elapsed Peak RSS Result
6.0.3, full check 47.87 s 2,868,588 KiB Exit 0, no diagnostics
7.0.2, full check 36.48 s 5,026,492 KiB Exit 2, out of memory; reproduced 3 times
7.0.2, --noCheck 1.42 s 685,224 KiB Exit 0

The --noCheck result suggests the expensive work happens during semantic checking. The Go traceback includes repeated github.com/microsoft/typescript-go/internal/checker.(*Relater).recursiveTypeRelatedTo frames and checker.(*Checker).getDiagnostics calls. We have not determined whether this is a leak or unusually expensive type relations. No public minimal repro is available yet because the project is private.

Activity

  1. ahejlsberg commented on Sep 24, 2026

    @ahejlsberg
    Member

    Can you try TS6 with the --stableTypeOrdering flag to see if this is related to type ordering?

  2. Amatewasu commented on Sep 25, 2026

    @Amatewasu
    Author

    Sure!

    tsc --version && tsc --stableTypeOrdering --noEmit
    
    Version 6.0.3
    
    <--- Last few GCs --->
    
    [6477:0x3ad97000]   114137 ms: Mark-Compact 4090.5 (4100.5) -> 4086.7 (4100.8) MB, pooled: 1.0 MB, 819.69 / 0.00 ms (average mu = 0.096, current mu = 0.010) allocation failure; scavenge might not succeed
    [6477:0x3ad97000]   114947 ms: Mark-Compact 4090.7 (4100.8) -> 4087.0 (4101.0) MB, pooled: 0.8 MB, 801.14 / 0.00 ms (average mu = 0.055, current mu = 0.012) allocation failure; scavenge might not succeed
    
    FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
    ----- Native stack trace -----
    
     1: 0x8256d0  [node]
     2: 0x82593d node::OOMErrorHandler(char const*, v8::OOMDetails const&) [node]
     3: 0xc91bbb  [node]
     4: 0xf02b05  [node]
     5: 0xf00d0c  [node]
     6: 0xef3da4  [node]
     7: 0xef3bcc  [node]
     8: 0xef355f  [node]
     9: 0xecf6ed  [node]
    10: 0x138c921  [node]
    11: 0x7770cfe7ea76 
    
    Aborted (core dumped)
  3. Amatewasu commented on Sep 25, 2026

    @Amatewasu
    Author

    I have asked a LLM to pinpoint the issue and do a repro. I have included CodeSandbox links. Here's its report:


    TypeScript 7.0.2 OOM investigation — findings

    Investigation of the issue reported against TypeScript 7.0.2 ("silently runs out of memory
    while typechecking a project that TypeScript 6.0.3 checks successfully").

    TL;DR

    TS 7.0.2 (the native Go compiler, @typescript/typescript-linux-x64) grows memory without
    bound — and without emitting any diagnostic — as soon as it checks any JSX element whose
    tag resolves to a React component
    , in a program where @react-three/fiber's global
    React.JSX.IntrinsicElements augmentation is loaded
    . The relation check between the
    component and the (massively augmented) JSX.ElementType union spirals into ever-larger
    reduced unions until the Go runtime dies. TS 6.0.3 checks the exact same program fine.

    A 5-line file reproduces it (see Minimal reproduction). Removing
    either ingredient (the fiber augmentation, or the component JSX element) makes TS 7.0.2 pass.

    Measurements

    Machine: Linux x86_64, 15 GB RAM, Node v20/v26, memory-capped runs via
    systemd-run --user --wait --collect -p MemoryMax=... -p MemorySwapMax=0 -p OOMPolicy=kill.
    Full project check = tsc --noEmit with the repository tsconfig.json (fresh, no
    tsconfig.tsbuildinfo).

    Program Compiler Result
    Full project (855 files, ~200k LOC) TS 6.0.3 Pass, 7.1 s, 1.06 GB peak RSS
    Full project TS 7.0.2 OOM-killed at 6 GB cap after ~30 s, zero output
    Full project TS 7.0.2 (11 GB cap) Go runtime fatal OOM at 9.2 GB peak RSS, exit 2
    Minimal repro (5 lines, below) TS 6.0.3 Pass, 11.3 s, 1.28 GB peak RSS
    Minimal repro TS 7.0.2 (2 GB cap) OOM-killed after ~28 s, zero output
    Minimal repro TS 7.0.2 (9 GB cap) Go fatal: out of memory: cannot allocate 4194304-byte block (9367126016 in use)

    The memory growth is not "three times TS6" as the issue's bounded run suggested — it is
    unbounded: the process consumes whatever cap it is given (2 GB, 6 GB, 9 GB …) and then dies
    without ever printing a diagnostic.

    Root cause chain

    1. tsconfig.json lists "@react-three/fiber" in compilerOptions.types, and several app
      files import it directly (Canvas, ThreeEvent, RootState, useFrame, … in
      src/components/editor/editorLibCir.tsx, src/components/toolboxes/flow-configuration/flow/user-strategy/positions-map-view.tsx,
      src/components/editor/stock-zone-settings/*, …). Either way the package's index.d.ts
      is loaded into the program.
    2. @react-three/fiber at the installed version 10.0.0-canary.fd3335d (pinned as
      ^10.0.0-alpha.4 in package.json) performs a global JSX augmentation:
      // node_modules/@react-three/fiber/dist/index.d.ts:1457-1472
      declare module 'react' {
        interface IntrinsicElements extends ThreeElements {}
      }
      declare module 'react/jsx-runtime' {
        interface IntrinsicElements extends ThreeElements {}
      }
      declare module 'react/jsx-dev-runtime' {
        interface IntrinsicElements extends ThreeElements {}
      }
      ThreeElements adds several hundred three-prefixed intrinsic elements.
    3. With jsx: "react-jsx", checking a component element such as <SvgIcon />
      (@mui/material 7.3.11) requires relating the component's type to
      JSX.ElementType — keyof IntrinsicElements | ComponentType<...> — which is now a
      very large union.
    4. TS 7.0.2's relater recurses through
      structuredTypeRelatedTo / unionOrIntersectionRelatedTo /
      getEffectiveConstraintOfIntersection, and inside that
      getReducedUnionType builds reduced unions mapped over 32 768 constituents
      (core.SameMap[...]({ptr, 0x8000, 0x8000}, ...) in the crash trace; the full-project
      crash shows the same path over 8 192 constituents), then computes
      getPropertiesOfUnionOrIntersectionType / getUnionOrIntersectionProperty on them.
      Memory grows without bound. This is the same stack the issue quotes
      (checker.(*Checker).checkSourceFile → getDiagnostics): the outer frames are just
      deferred JSX-node checking of the file that happens to be checked when the limit hits.
    5. TS 6.0.3 performs the same relation check with bounded work (1.28 GB on the minimal
      repro), i.e. this is a TS 7 checker regression/deficiency, not a project pathology.

    Crash stack (TS 7.0.2, minimal repro, 9 GB cap — trimmed):

    runtime: out of memory: cannot allocate 4194304-byte block (9367126016 in use)
    fatal error: out of memory
    ...
    checker.(*Checker).getUnionOrIntersectionProperty(...)
    checker.(*Checker).getPropertiesOfUnionOrIntersectionType(...)
    core.SameMap[...]({..., 0x8000, 0x8000}, ...)          // 32768-constituent union being reduced
    checker.(*Checker).getReducedUnionType(...)
    checker.(*Checker).getEffectiveConstraintOfIntersection(...)
    checker.(*Relater).structuredTypeRelatedTo(...)
    checker.(*Relater).isRelatedToEx(...)
    checker.(*Relater).unionOrIntersectionRelatedTo(...)
    ... (isRelatedToEx/structuredTypeRelatedTo cycles) ...
    checker.(*Checker).checkJsxOpeningLikeElementOrOpeningFragment(...)
    checker.(*Checker).checkJsxSelfClosingElementDeferred(...)
    checker.(*Checker).checkDeferredNode(...)
    checker.(*Checker).checkDeferredNodes(...)
    checker.(*Checker).checkSourceFile(...)
    checker.(*Checker).getDiagnostics(...)
    

    Minimal reproduction

    index.tsx:

    import '@react-three/fiber';
    import SvgIcon from '@mui/material/SvgIcon';
    
    export function Icon() {
      return <SvgIcon />;
    }

    tsconfig.json:

    {
      "compilerOptions": {
        "strict": true,
        "noEmit": true,
        "jsx": "react-jsx",
        "module": "esnext",
        "moduleResolution": "bundler",
        "target": "esnext",
        "lib": ["esnext", "dom", "dom.iterable"],
        "skipLibCheck": true,
        "esModuleInterop": true,
        "isolatedModules": true
      },
      "include": ["index.tsx"]
    }

    Dependencies involved: typescript@7.0.2, react@19.3.0 / @types/react@19.3.0,
    @mui/material@7.3.11, @react-three/fiber@10.0.0-canary.fd3335d.

    Run under a memory cap (so the leak is observable instead of eating the whole machine):

    systemd-run --user --wait --collect -p MemoryMax=2G -p MemorySwapMax=0 -p OOMPolicy=kill \
      -- ./node_modules/.bin/tsc --noEmit -p .
    

    Results: TS 7.0.2 is OOM-killed in ~30 s having consumed the whole cap with no output.
    TS 6.0.3 on the same program finishes in ~11 s using ~1.3 GB.

    Controls (all with TS 7.0.2, same cap):

    Variation Result
    Remove the import '@react-three/fiber' line Pass, 83 MB
    Keep fiber, but JSX tag is an intrinsic element (<div />) Pass, 98 MB
    Keep fiber, JSX tag is any MUI component (<SvgIcon />, <Box sx={{...}} />) OOM
    Reference SvgIconProps/SvgIconProps['sx'] without JSX Pass
    Same repro, TS 6.0.3 Pass, 1.28 GB

    So the trigger is precisely: fiber's IntrinsicElements augmentation + a component (non-intrinsic) JSX element.

    Live repro sandboxes

    The same 5-line program is set up on CodeSandbox (nodebox/Node VM template). The two
    sandboxes contain identical files; the only difference is the typescript version in
    package.json:

    Sandbox Compiler Link
    ts7-oom-repro (fails) TS 7.0.2 https://codesandbox.io/s/7xxcd5
    ts6-control (passes) TS 6.0.3 https://codesandbox.io/s/k8nxjg

    The start script is tsc --noEmit, so the typecheck runs automatically on boot; it can
    be re-run at any time from the sandbox terminal with npm run typecheck. Expected
    behavior: the TS 7.0.2 sandbox's check grows memory without bound until the VM's OOM
    killer terminates it (no diagnostics printed), while the TS 6.0.3 sandbox completes the
    check cleanly with exit 0.

  4. Amatewasu commented on Sep 28, 2026

    @Amatewasu
    Author

    I've continued to ask a LLM to investigate.


    Follow-up to my earlier report: removing the R3F JSX augmentation fixed the original 5-line repro, but the full project still OOMs. There is a second, independent trigger with no React and no fiber in it — just three/tsl. Three lines are enough:

    import { uv, vec2 } from 'three/tsl';
    
    export const x = uv().div(vec2(1, 1));

    Only dependencies: typescript@7.0.2 and three@0.185.1, default-ish strict tsconfig, tsc --noEmit. TS 7.0.2 grows until the memory cap kills it (2 GiB, ~30 s, zero diagnostics). TS 6.0.3 checks the same file fine.

    The trigger is calling a method on a TSL node (.div, .add, .mul, …) or passing a node-typed value into certain TSL functions (mix(float(1), ...)). Plain calls like uv(), uv().x, texture(t, uv()), step(...), mod(...) all pass; casting the receiver to any also makes the repro pass. The TSL node types are built through heavily overloaded generic builders, and the crash trace is the same relater loop as the original report, running inside variance computation:

    getPropertiesOfUnionOrIntersectionType
    getReducedUnionType
    getEffectiveConstraintOfIntersection
    structuredTypeRelatedTo / recursiveTypeRelatedTo   (cycles)
    

    Since this repro has no JSX augmentation at all, and --stableTypeOrdering also made TS 6 OOM earlier in this thread, both cases may share the same union-reduction/variance path rather than anything specific to the React or R3F declaration shapes.

  5. added 2 commits that reference this issue on Oct 20, 2026
    5b7db18
    f326b55
  6. changed the title [-]TypeScript 7.0.2 silently runs out of memory while typechecking a project that TypeScript 6.0.3 checks successfully[/-] [+][7.0 regression] Unbounded type relation expansion during variance computation causes OOM[/+] on Oct 26, 2026
  7. added 2 commits that reference this issue on Oct 27, 2026
    ee7a83c
    ee6b93d
  8. Amatewasu commented on Oct 27, 2026

    @Amatewasu
    Author

    I wonder why no other people are having this issue. I would have expected other people to comment on this issue?

  9. added 2 commits that reference this issue on Oct 29, 2026
    e8e1c1d
    fd22d95
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs More InfoThe issue still hasn't been fully clarified

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions