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

Possible regression: Maximum call stack size exceeded when migrating from 3.7.5 to latest version #39059

Open
nscarcella opened this issue Jun 13, 2020 · 6 comments
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue Rescheduled This issue was previously scheduled to an earlier milestone

Comments

@nscarcella
Copy link

After migrating from 3.7.5 to any later version the attached code makes the compiler fails with the following error:

RangeError: Maximum call stack size exceeded
    at Object.some (.../node_modules/typescript/lib/tsc.js:587:25)
    at isTypeReferenceWithGenericArguments (.../node_modules/typescript/lib/tsc.js:41098:59)
    at getRelationKey (.../node_modules/typescript/lib/tsc.js:41129:17)
    at recursiveTypeRelatedTo (.../node_modules/typescript/lib/tsc.js:40079:26)
    at isRelatedTo (.../node_modules/typescript/lib/tsc.js:39814:38)
    at typeRelatedToSomeType (.../node_modules/typescript/lib/tsc.js:39979:35)
    at isRelatedTo (.../node_modules/typescript/lib/tsc.js:39800:34)
    at eachTypeRelatedToType (.../node_modules/typescript/lib/tsc.js:40022:35)
    at isRelatedTo (.../node_modules/typescript/lib/tsc.js:39796:25)
    at compareTypePredicateRelatedTo (.../node_modules/typescript/lib/tsc.js:39353:46)

The same file compiles fine in 3.7.5 and I can't find a way to adapt it to the newer versions.

TypeScript Version: any version above 3.7.5

Search Terms: "Maximum call stack size exceeded" "Range Error" "Regression" "3.8"

Config:

{
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "outDir": "dist/temp",
    "lib": [
      "es2017"
    ],
    "declaration": true,
    "strict": true,
    "removeComments": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "experimentalDecorators": true
  },
  "exclude": [
    "node_modules",
    "dist"
  ]
}

Code
Sorry about the lengthy code, I removed as much unrelated logic as I could and trimmed all methods and fields from the classes so it will be as short as possible while still triggering the error consistently. Removing any of the seemingly trivial classes left prevents the error (not sure why, though) but it's, of course, not a valid option.

const { assign } = Object

export type Name = string
export type List<T> = ReadonlyArray<T>
export type Payload<T> = Record<string, unknown>

export type Stage = Raw | Filled | Linked
export abstract class Raw { protected readonly rawTag = 'Raw' }
export abstract class Filled extends Raw { protected readonly filledTag = 'Filled' }
export abstract class Linked extends Filled { protected readonly linkedTag = 'Linked' }
export type Final = Linked

type Stageable<S extends Stage, C extends Stage, T> = S extends C ? T : T | undefined
export type Fillable<S extends Stage, T> = Stageable<S, Filled, T>
export type Linkable<S extends Stage, T> = Stageable<S, Linked, T>

export type Kind = Node['kind']
export type Category = 'Entity' | 'Module' | 'Sentence' | 'Expression'
export type NodeOfKind<K extends Kind, S extends Stage> = Extract<Node<S>, { kind: K }>
export type NodeOfKindOrCategory<Q extends Kind | Category, S extends Stage> =
  Q extends Kind ? NodeOfKind<Q, S> :
  never

export type Node<S extends Stage = Final>
  = Parameter<S>
  | NamedArgument<S>
  | Import<S>
  | Body<S>
  | Catch<S>
  | Entity<S>
  | DescribeMember<S>
  | ClassMember<S>
  | Sentence<S>

abstract class $Node<S extends Stage> {
  readonly stage?: S
  abstract readonly kind: Kind

  constructor(payload: Record<string, unknown>) {
    assign(this, payload)
  }
 
  is<Q extends Kind | Category>(kindOrCategory: Q): this is NodeOfKindOrCategory<Q, S> {
    return this.kind === kindOrCategory
  }
}

export class Parameter<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Parameter'
}

export class NamedArgument<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'NamedArgument'
  readonly value!: Expression<S>
}

export class Import<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Import'
  readonly entity!: Reference<S>
}

export class Body<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Body'
  readonly sentences!: List<Sentence<S>>
}

export type Entity<S extends Stage = Final>
  = Package<S>
  | Program<S>
  | Test<S>
  | Describe<S>
  | Module<S>
  | Variable<S>


export class Package<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Package'
  readonly imports!: List<Import<S>>
  readonly members!: List<Entity<S>>
}

export class Program<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Program'
  readonly body!: Body<S>
}

export class Test<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Test'
  readonly body!: Body<S>
}

export class Describe<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Describe'
  readonly members!: List<DescribeMember<S>>

  tests(): List<Test<S>> { return this.members.filter((member): member is Test<S> => member.is('Test')) }
  methods(): List<Method<S>> { return this.members.filter((member): member is Method<S> => member.is('Method')) }
  variables(): List<Variable<S>> { return this.members.filter((member): member is Variable<S> => member.is('Variable')) }
  fixtures(): List<Fixture<S>> { return this.members.filter((member): member is Fixture<S> => member.is('Fixture')) }
}

export class Variable<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Variable'
  readonly value!: Fillable<S, Expression<S>>

  is<Q extends Kind | Category>(kindOrCategory: Q): this is NodeOfKindOrCategory<Q, S> {
    return [this.kind, 'Sentence', 'Entity'].includes(kindOrCategory)
  }
}

export type Module<S extends Stage = Final> = Class<S> | Singleton<S> | Mixin<S>

export class Class<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Class'
  readonly mixins!: List<Reference<S>>
  readonly members!: List<ClassMember<S>>
  readonly superclass!: Fillable<S, Reference<S> | null>
}

export class Singleton<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Singleton'
  readonly mixins!: List<Reference<S>>
  readonly members!: List<ObjectMember<S>>
  readonly superCall!: Fillable<S, {
    superclass: Reference<S>,
    args: List<Expression<S>> | List<NamedArgument<S>>
  }>
}

export class Mixin<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Mixin'
  readonly mixins!: List<Reference<S>>
  readonly members!: List<ObjectMember<S>>
}

export type ObjectMember<S extends Stage = Final> = Field<S> | Method<S>
export type ClassMember<S extends Stage = Final> = Constructor<S> | ObjectMember<S>
export type DescribeMember<S extends Stage = Final> = Variable<S> | Fixture<S> | Test<S> | Method<S>

export class Field<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Field'
  readonly value!: Fillable<S, Expression<S>>
}

export class Method<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Method'
  readonly parameters!: List<Parameter<S>>
  readonly body?: Body<S>
}

export class Constructor<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Constructor'
  readonly parameters!: List<Parameter<S>>
  readonly body!: Body<S>
  readonly baseCall?: { callsSuper: boolean, args: List<Expression<S>> }
}

export class Fixture<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Fixture'
  readonly body!: Body<S>
}

export type Sentence<S extends Stage = Final> = Variable<S> | Return<S> | Assignment<S> | Expression<S>

export class Return<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Return'
  readonly value?: Expression<S>
}

export class Assignment<S extends Stage = Final> extends $Node<S> {
  readonly kind = 'Assignment'
  readonly variable!: Reference<S>
  readonly value!: Expression<S>
}

export type Expression<S extends Stage = Final>
  = Reference<S>
  | Self<S>
  | Send<S>
  | Super<S>
  | New<S>
  | If<S>
  | Throw<S>
  | Try<S>

abstract class $Expression<S extends Stage> extends $Node<S> {
  is<Q extends Kind | Category>(kindOrCategory: Q): this is NodeOfKindOrCategory<Q, S> {
    return kindOrCategory === 'Expression' || super.is(kindOrCategory)
  }
}

export class Reference<S extends Stage = Final> extends $Expression<S> {
  readonly kind = 'Reference'
  readonly name!: Name
}

export class Self<S extends Stage = Final> extends $Expression<S> {
  readonly kind = 'Self'
}

export class Send<S extends Stage = Final> extends $Expression<S> {
  readonly kind = 'Send'
  readonly receiver!: Expression<S>
  readonly message!: Name
  readonly args!: List<Expression<S>>
}

export class Super<S extends Stage = Final> extends $Expression<S> {
  readonly kind = 'Super'
  readonly args!: List<Expression<S>>
}

export class New<S extends Stage = Final> extends $Expression<S> {
  readonly kind = 'New'
  readonly instantiated!: Reference<S>
  readonly args!: List<Expression<S>> | List<NamedArgument<S>>
}

export class If<S extends Stage = Final> extends $Expression<S> {
  readonly kind = 'If'
  readonly thenBody!: Body<S>
  readonly elseBody!: Fillable<S, Body<S>>
}

export class Throw<S extends Stage = Final> extends $Expression<S> {
  readonly kind = 'Throw'
}

export class Try<S extends Stage = Final> extends $Expression<S> {
  readonly kind = 'Try'
}

export class Catch<S extends Stage = Final> extends $Expression<S> {
  readonly kind = 'Catch'
}

Expected behavior: The code builds successfully.

Actual behavior: The compilation fails.

@demisx
Copy link

demisx commented Feb 22, 2022

We got similar error upgrading TS from 4.5.2 -> 4.5.5. Had to roll back:

[api] RangeError: Maximum call stack size exceeded
[api] RangeError: Maximum call stack size exceeded
[api]     at isRelatedTo (/usr/src/node_modules/typescript/lib/typescript.js:63309:33)
[api]     at typeArgumentsRelatedTo (/usr/src/node_modules/typescript/lib/typescript.js:63768:39)
[api]     at relateVariances (/usr/src/node_modules/typescript/lib/typescript.js:64444:34)
[api]     at structuredTypeRelatedToWorker (/usr/src/node_modules/typescript/lib/typescript.js:64378:46)
[api]     at structuredTypeRelatedTo (/usr/src/node_modules/typescript/lib/typescript.js:63920:30)
[api]     at recursiveTypeRelatedTo (/usr/src/node_modules/typescript/lib/typescript.js:63887:64)
[api]     at isRelatedTo (/usr/src/node_modules/typescript/lib/typescript.js:63403:34)
[api]     at isPropertySymbolTypeRelated (/usr/src/node_modules/typescript/lib/typescript.js:64642:24)
[api]     at propertyRelatedTo (/usr/src/node_modules/typescript/lib/typescript.js:64675:31)
[api]     at propertiesRelatedTo (/usr/src/node_modules/typescript/lib/typescript.js:64876:43)

@zamb3zi
Copy link

zamb3zi commented Mar 31, 2022

I'm also getting a "Maximum call stack size exceeded" error on upgrade from 4.6.0-beta to 4.6.1-rc through 4.63.
I'm working in a large proprietary codebase which I can't share.
I'd love some tips on how to debug tsc to find the types it's choking on.

@MarcoGlauser
Copy link

We're also running into this issue with our Angular project. For us it happened when moving for 4.6.1-rc to 4.6.2.
Adding --max_old_space_size=4096 works for one off builds but live reloading crashes after a couple of iterations.

Here are the memory profiles for 4.5.5, 4.6.1-rc, 4.6.2 :
ts-4..zip
You can see 4.5.5 and 4.6.1-rc behaving the same, but 4.6.2 not releasing memory and crashing instead. Let me know if you need any more information.

@DerZyklop
Copy link

here you have @nscarcella ’s code in playground.

@oleg-tsybulsky
Copy link

Angular CLI: 14.2.0
Node: 16.13.0
Package Manager: npm 7.22.0
OS: win32 x64

Angular: 14.2.0
... animations, cdk, cli, common, compiler, compiler-cli, core
... forms, language-service, material, platform-browser
... platform-browser-dynamic, router

Package Version

@angular-devkit/architect 0.1402.0
@angular-devkit/build-angular 14.2.0
@angular-devkit/core 14.2.0
@angular-devkit/schematics 14.2.0
@schematics/angular 14.2.0
rxjs 7.5.6
typescript 4.8.2

<--- Last few GCs --->

[15680:00000133705CBA10] 267585 ms: Scavenge (reduce) 8065.7 (8244.6) -> 8065.2 (8244.6) MB, 14.7 / 0.0 ms (average mu = 0.218, current mu = 0.028) allocation failure
[15680:00000133705CBA10] 267598 ms: Scavenge (reduce) 8066.0 (8244.8) -> 8065.4 (8244.8) MB, 12.2 / 0.0 ms (average mu = 0.218, current mu = 0.028) allocation failure

<--- JS stacktrace --->

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
1: 00007FF7413B013F v8::internal::CodeObjectRegistry::~CodeObjectRegistry+112495
2: 00007FF74133F396 DSA_meth_get_flags+65526
3: 00007FF74134024D node::OnFatalError+301
4: 00007FF741C719EE v8::Isolate::ReportExternalAllocationLimitReached+94
5: 00007FF741C5BECD v8::SharedArrayBuffer::Externalize+781
6: 00007FF741AFF61C v8::internal::Heap::EphemeronKeyWriteBarrierFromCode+1468
7: 00007FF741B0C2C9 v8::internal::Heap::PublishPendingAllocations+1129
8: 00007FF741B0929A v8::internal::Heap::PageFlagsAreConsistent+2842
9: 00007FF741AFBF19 v8::internal::Heap::CollectGarbage+2137
10: 00007FF741AFA0D0 v8::internal::Heap::AllocateExternalBackingStore+2000
11: 00007FF741B1EA06 v8::internal::Factory::NewFillerObject+214
12: 00007FF741851CD5 v8::internal::DateCache::Weekday+1797
13: 00007FF741CFF3E1 v8::internal::SetupIsolateDelegate::SetupHeap+494417
14: 00007FF741C942EE v8::internal::SetupIsolateDelegate::SetupHeap+55902
15: 0000013373C899F6

@MikeMatusz
Copy link

I know this was the issue that was linked from the angular-cli thread, but I feel like the "heap out of memory" issue is probably different from the "maximum call stack size exceeded" mentioned by the OP. We were also seeing the memory leak with Angular 14.1, though tentatively it seems to be behaving better on 14.2.4, maybe related to angular/angular#47508?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue Rescheduled This issue was previously scheduled to an earlier milestone
Projects
None yet
Development

Successfully merging a pull request may close this issue.