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

Crash with recursive conditional type during serialization #43529

Closed
jBernavaPrah opened this issue Apr 5, 2021 · 8 comments
Closed

Crash with recursive conditional type during serialization #43529

jBernavaPrah opened this issue Apr 5, 2021 · 8 comments
Assignees
Labels
Bug A bug in TypeScript Crash For flagging bugs which are compiler or service crashes or unclean exits, rather than bad output Rescheduled This issue was previously scheduled to an earlier milestone

Comments

@jBernavaPrah
Copy link

Bug Report

Hi guys,
I am not sure if it is a bug but it is a strange behaviour surely.
I have created some Types that help me during the development of my ReactJs app.

I can use those types separately and they show output result quickly and use the normal quantity of memory.

Although, when I try to reuse those types with other Types, the compilation of typescript on my ReactJs app is not completed and after some time the node that runs yarn start crashes because it has reached the maximal memory allowed (it can be 2Gb or 8Gb, it is just a matter of time before the crash). If I comment out those types, the ReactJs compilation is completed as expected.

I think I have recreated the same behaviour also on Playground. In this case, the typescript worker used by Chrome will grow to 1.5Gb, without showing information on the popup about the Type.

I have created this Video to understand better what I mean by it.

On the playground I have put what I think is the relevant code from my project, to show this error.

🔎 Search Terms

typescript, recursion, JavaScript heap out of memory, ReactJs

🕗 Version & Regression Information

Playground and typescript npm 4.2.5 and Nightly.

⏯ Playground Link

Playground link with relevant code

💻 Code

// relevant code on Playground
// from line line #129 
type UseQueryOptions<T extends Base, K extends AllKeys<T, 4> > = Expand<T, K> 

type UseQueryOptions2<T , K  > = Expand_<T, K>  // line #129
type UseQueryOptions3<T , K  > = Expand_<T, K> extends infer O ? O : never  

type ExpandResult<T,K> = Expand_<T, K> extends infer O ? O : never
type UseQueryOptions4<T , K  > = ExpandResult<T,K>

🙁 Actual behavior

Every time I try to put together multiple Types, the yarn start (react-scripts start) is stuck on: Files successfully emitted, waiting for typecheck results... and after the memory node reaches the maximal allowed (can be 2Gb or 8Gb) it crashes with:

<--- Last few GCs --->

[16365:0x1046ca000]   448881 ms: Mark-sweep 2028.9 (2059.0) -> 2020.9 (2059.3) MB, 766.2 / 0.0 ms  (average mu = 0.132, current mu = 0.008) allocation failure scavenge might not succeed
[16365:0x1046ca000]   449641 ms: Mark-sweep 2028.9 (2059.3) -> 2020.9 (2059.3) MB, 754.6 / 0.0 ms  (average mu = 0.073, current mu = 0.007) allocation failure scavenge might not succeed


<--- JS stacktrace --->

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 0x1012e4da5 node::Abort() (.cold.1) [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]
 2: 0x1000a6239 node::Abort() [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]
 3: 0x1000a639f node::OnFatalError(char const*, char const*) [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]
 4: 0x1001e9007 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]
 5: 0x1001e8fa3 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]
 6: 0x100397e95 v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]
 7: 0x10039995a v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]
 8: 0x100395029 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]
 9: 0x1003928c1 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]
10: 0x1003a115a v8::internal::Heap::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]
11: 0x1003a11e1 v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]
12: 0x10036eb87 v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]
13: 0x1006ed8d8 v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]
14: 0x100a7a239 Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/Users/jure.prah/.nvm/versions/node/v14.16.0/bin/node]

🙂 Expected behavior

My intention is to be able to use the recursions Type together with other types.

@andrewbranch
Copy link
Member

I get a max call stack size error when hovering UseQueryOptions, but can’t trigger the out of memory crash. Could you share a project / example repo that reliably crashes this way?

@andrewbranch andrewbranch added the Needs More Info The issue still hasn't been fully clarified label Apr 5, 2021
@jBernavaPrah
Copy link
Author

Thank you for your response. And sorry for the delay.

Here you will found the example project.

On src/query/index.ts, you will find the interfaces, types and functions that I use during development.
On src/components/TestFetchPage.tsx, you will find an example of how I use it on my project.

The master branch contains two commits:

  • The current Master commit, that will trigger the memory crash;
  • And the commit that shows that, if I comment lines src/queries/index.ts:73 and src/queries/index.ts:74 and uncomment the line src/queries/index.ts:76, the crash is gone and yarn runs normally.

I hope I'm helping you with this. Otherwise, do not hesitate to ask for any additional information.

@andrewbranch andrewbranch added Needs Investigation This issue needs a team member to investigate its status. and removed Needs More Info The issue still hasn't been fully clarified labels Apr 6, 2021
@andrewbranch andrewbranch self-assigned this Apr 6, 2021
@andrewbranch andrewbranch added this to the TypeScript 4.3.1 milestone Apr 6, 2021
@andrewbranch
Copy link
Member

Reduced case:

export type CanBeExpanded<T extends object> = {
  value: T
}

type Expand__<O, N, Depth> =
  N extends Depth ?
      unknown :
      O extends CanBeExpanded<any> ?
          Expand__<O['value'], N, Depth> :
          O

export type UseQueryOptions<T> = Expand__<T, 4, 2>

@jBernavaPrah
Copy link
Author

Hi @andrewbranch, could you please give me a temporary solution that I could use until the v4.3.1 is fixed?

@andrewbranch
Copy link
Member

Try using types understandable by mere mortal humans? 😅 I don’t know for sure yet because I haven’t fixed it yet. Even once I understand exactly what’s going wrong, it may be difficult to apply that knowledge back to the massive types you have.

@jBernavaPrah
Copy link
Author

jBernavaPrah commented Apr 8, 2021

ahah! I see your point! :)

I have created a more simplified example that works.

type KeysCanBeExpanded_<T = never, Depth extends number[] = number[], Deep2 extends number[] = number[]> = 

    // This is to limit the deep of subcalls, when T is a object. 
    4 extends Depth['length'] ? never :
        
        // This is a global limit that prevent the infinite recursion.
        // If you comment the next line (16) the KeysCanBeExpanded<T> (line 32) will go on infinite recursion.
        8 extends Deep2['length'] ? never :

    T extends object ?
        // The first Depth is the cause of infinite recursion on KeysCanBeExpanded<T>
        KeysCanBeExpanded_<T, Depth, [1,...Deep2]> 
        :
        T

export type KeysCanBeExpanded<T> = KeysCanBeExpanded_<T, [], []>

As I have written in the comments above if you comment the line 8 extends Deep2['length'] ? never :, then the infinite loop will be again triggered.

As I was thinking the problem is related to some knowledge that I was missing. I don't understand why KeysCanBeExpanded is "executed".
I was expecting that KeysCanBeExpanded extends the KeysCanBeExpanded_<T>, not that execute it.

I really appreciate your help :)

Here you can find the playground.

@andrewbranch andrewbranch added Bug A bug in TypeScript Crash For flagging bugs which are compiler or service crashes or unclean exits, rather than bad output and removed Needs Investigation This issue needs a team member to investigate its status. labels Apr 8, 2021
@andrewbranch andrewbranch changed the title Problem with recursion and multiple types used together. Crash with recursive conditional type during serialization Apr 8, 2021
@andrewbranch
Copy link
Member

@jBernavaPrah, I’ve had a chance to debug this and see what’s going wrong, which led me to a workaround for you. The problem comes when we’re trying to serialize these recursive types. One way that we can avoid serializing them is if we can substitute a type alias for them. So, the more you can break these large recursive types down into smaller pieces, the more we can substitute in the names of those individual pieces and break the loop. Here I’ve extracted one part of Expand__ into Expand__2 and it solves the problem.

Of course, this is still a bug on our end, but this should get you unstuck in the meantime.

@andrewbranch
Copy link
Member

Fixed by #43733

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Crash For flagging bugs which are compiler or service crashes or unclean exits, rather than bad output Rescheduled This issue was previously scheduled to an earlier milestone
Projects
None yet
Development

No branches or pull requests

2 participants