microsoft / microsoft/TypeScript

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

未关闭
#39,059 6 条评论 0 个 reaction 已指派 1 人 在 GitHub 查看

@weswigham 已经在做这个了。

开始于 2020年6月16日。

Bug Domain: check: Type Circularity Rescheduled
主要语言
Go
星标
111k
派生
14.4k
平均合并
1 天 19 小时
30 天内合并 PR
117

描述

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.

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。