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

[tsc] Regression causing 'Maximum call stack size exceeded' #33460

Closed
lookfirst opened this issue Sep 17, 2019 · 17 comments
Closed

[tsc] Regression causing 'Maximum call stack size exceeded' #33460

lookfirst opened this issue Sep 17, 2019 · 17 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

Comments

@lookfirst
Copy link

lookfirst commented Sep 17, 2019

Hopefully this one can get fixed quickly since it is a pretty major regression. I spent a lot of time figuring out exactly which version range is causing the failure as well as trying to figure out a minimal test case.

TypeScript Version:

Failure at version: 3.6.0-dev.20190807 and above.
Everything works: 3.6.0-dev.20190806 and lower.

Code

tsc-fail.zip

To run:

yarn
./node_modules/.bin/tsc

Expected behavior:

No error.

Actual behavior:

tsc-fail/node_modules/typescript/lib/tsc.js:75299
                throw e;
                ^

RangeError: Maximum call stack size exceeded
    at Object.getObjectFlags (tsc-fail/node_modules/typescript/lib/tsc.js:10227:28)
    at isExcessPropertyCheckTarget (tsc-fail/node_modules/typescript/lib/tsc.js:42328:51)
    at Object.every (tsc-fail/node_modules/typescript/lib/tsc.js:295:22)
    at isExcessPropertyCheckTarget (tsc-fail/node_modules/typescript/lib/tsc.js:42331:44)
    at isKnownProperty (tsc-fail/node_modules/typescript/lib/tsc.js:42317:52)
    at hasCommonProperties (tsc-fail/node_modules/typescript/lib/tsc.js:37523:21)
    at isRelatedTo (tsc-fail/node_modules/typescript/lib/tsc.js:36276:58)
    at typeRelatedToSomeType (tsc-fail/node_modules/typescript/lib/tsc.js:36458:35)
    at isRelatedTo (tsc-fail/node_modules/typescript/lib/tsc.js:36300:34)
    at isPropertySymbolTypeRelated (tsc-fail/node_modules/typescript/lib/tsc.js:37137:28)
@lookfirst
Copy link
Author

Maybe related: #32582

@lookfirst
Copy link
Author

Running with 3.6.3 the error has changed a bit...

RangeError: Maximum call stack size exceeded
    at recursiveTypeRelatedTo (/node_modules/typescript/lib/tsc.js:36800:44)
    at isRelatedTo (/node_modules/typescript/lib/tsc.js:36477:38)
    at typeRelatedToSomeType (/node_modules/typescript/lib/tsc.js:36614:35)
    at isRelatedTo (/node_modules/typescript/lib/tsc.js:36456:34)
    at eachTypeRelatedToType (/node_modules/typescript/lib/tsc.js:36736:35)
    at isRelatedTo (/node_modules/typescript/lib/tsc.js:36452:25)
    at isPropertySymbolTypeRelated (/node_modules/typescript/lib/tsc.js:37293:28)
    at propertyRelatedTo (/node_modules/typescript/lib/tsc.js:37333:31)
    at propertiesRelatedTo (/node_modules/typescript/lib/tsc.js:37434:43)
    at isRelatedTo (/node_modules/typescript/lib/tsc.js:36468:34)

@lookfirst
Copy link
Author

Version 3.7.0-dev.20190917

/node_modules/typescript/lib/tsc.js:75802
                throw e;
                ^

RangeError: Maximum call stack size exceeded
    at isUnconstrainedTypeParameter (/node_modules/typescript/lib/tsc.js:37850:46)
    at /node_modules/typescript/lib/tsc.js:37854:105
    at Object.some (/node_modules/typescript/lib/tsc.js:673:25)
    at isTypeReferenceWithGenericArguments (/node_modules/typescript/lib/tsc.js:37854:58)
    at getRelationKey (/node_modules/typescript/lib/tsc.js:37884:17)
    at recursiveTypeRelatedTo (/node_modules/typescript/lib/tsc.js:36895:26)
    at isRelatedTo (/node_modules/typescript/lib/tsc.js:36568:38)
    at eachTypeRelatedToType (/node_modules/typescript/lib/tsc.js:36827:35)
    at isRelatedTo (/node_modules/typescript/lib/tsc.js:36543:25)
    at isPropertySymbolTypeRelated (/node_modules/typescript/lib/tsc.js:37393:28)    

@RyanCavanaugh RyanCavanaugh added Bug A bug in TypeScript Crash For flagging bugs which are compiler or service crashes or unclean exits, rather than bad output labels Sep 17, 2019
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.7.0 milestone Sep 17, 2019
@koskimas
Copy link

koskimas commented Sep 22, 2019

This also happens with objection.js typings. I don't know if this helps, but you can reproduce it like this:

git clone [email protected]:Vincit/objection.js.git
git checkout typescript-issue-33460
npm install
npm install [email protected]
npm run test-typings

Using the package.json version 3.5.3 works

npm install [email protected]
npm run test-typings

I was able to find out that this monstrosity of a type is the culprit:

@koskimas
Copy link

koskimas commented Sep 22, 2019

Oh, the original reproduction was also done using objection typings 😄

@lookfirst
Copy link
Author

@koskimas I'm also sorry to say that I've now moved on from Objection to MikroORM. =(

@koskimas
Copy link

koskimas commented Sep 25, 2019

I just found out that changing this

  type GraphParameters = {
    '#dbRef'?: MaybeCompositeId;
    '#ref'?: string;
    '#id'?: string;
  };

  type PartialModelGraph<T> = {
    [K in NonFunctionPropertyNames<T>]?: Exclude<T[K], undefined> extends Model
      ? PartialModelGraph<Exclude<T[K], undefined>>
      : Exclude<T[K], undefined> extends Array<infer I>
      ? (I extends Model ? PartialModelGraph<I>[] : (T[K] | NonPrimitiveValue))
      : (T[K] | NonPrimitiveValue);
  } &
    GraphParameters;

into this

  type GraphParameters = {
    '#dbRef'?: MaybeCompositeId;
    '#ref'?: string;
    '#id'?: string;
  };

  type PartialModelGraph<M, T = M & GraphParameters> = {
    [K in NonFunctionPropertyNames<T>]?: Exclude<T[K], undefined> extends Model
      ? PartialModelGraph<Exclude<T[K], undefined>>
      : Exclude<T[K], undefined> extends Array<infer I>
      ? (I extends Model ? PartialModelGraph<I>[] : (T[K] | NonPrimitiveValue))
      : (T[K] | NonPrimitiveValue);
  }

fixes the problem.

Also note that @lookfirst's reproduction uses objection 1.6.9 while the above types and my repro is with objection 2.0 typings which have been completely rewritten from scratch. So this fix only applies to my repro.

koskimas added a commit to Vincit/objection.js that referenced this issue Sep 25, 2019
@technicallyfeasible
Copy link

I just ran into the same problem on v3.7.2

RangeError: Maximum call stack size exceeded
    at isRelatedTo (node_modules\typescript\lib\tsc.js:1:1)
    at typeRelatedToSomeType (node_modules\typescript\lib\tsc.js:38604:35)
    at isRelatedTo (node_modules\typescript\lib\tsc.js:38438:34)
    at eachTypeRelatedToType (node_modules\typescript\lib\tsc.js:38726:35)
    at isRelatedTo (node_modules\typescript\lib\tsc.js:38434:25)
    at isPropertySymbolTypeRelated (node_modules\typescript\lib\tsc.js:39293:28)
    at propertyRelatedTo (node_modules\typescript\lib\tsc.js:39333:31)
    at propertiesRelatedTo (node_modules\typescript\lib\tsc.js:39437:43)
    at isRelatedTo (node_modules\typescript\lib\tsc.js:38450:34)
    at typeRelatedToSomeType (node_modules\typescript\lib\tsc.js:38604:35)

The problem appeared when using "morphism" together with "sequelize" and I managed to reproduce it with this:
(sorry, it's still a bit lengthy)

function morphism<TSchema = Schema<DestinationFromSchema<Schema>, SourceFromSchema<Schema>>, Source extends SourceFromSchema<TSchema> = SourceFromSchema<TSchema>>(schema: TSchema, data: Source[]): DestinationFromSchema<TSchema>[] {
  throw new Error();
}

type SourceFromSchema<T> = T extends Schema<unknown, infer U> ? U : never;
type DestinationFromSchema<T> = T extends Schema<infer U> ? U : never;

declare const SCHEMA_OPTIONS_SYMBOL: unique symbol;

type Schema<Target = any, Source = any> = {
  [destinationProperty in keyof Target]?: Schema<Target[destinationProperty], Source>;
} & {
  [SCHEMA_OPTIONS_SYMBOL]?: any;
};

export class Sequelize {
  public Sequelize!: typeof Sequelize;
}

interface MyModel extends Sequelize {
  vehicleId: string;
}

export interface MyModelDTO {
  id: string;
}

export const MyModelDTOVehicleModel: Schema<MyModel, MyModelDTO> = {
  vehicleId: 'id',
};

const output: MyModel[] = morphism(MyModelDTOVehicleModel, [{
  id: 'ID',
}]);

As mentioned before it works in version 3.5.3.

@lookfirst
Copy link
Author

Just use MikroORM v3, seriously.

@orta
Copy link
Contributor

orta commented Mar 25, 2020

Spent this morning looking into this:

  • Still happens with master
  • Probably is due to Improve excess property checking for intersections #32582 (as mentioned above)
  • getUnmatchedProperties doesn't seem to show in any stack traces (likely because it's a generator func which is transpile out) but it's where a lot of the looping goes through

The loop seems to roughly be (based on getUnmatchedProperties):

  • R -> P
    - hits the Intersection|IncludesNonWideningType with R -> P as
    '{ '#id'?: string; '#ref'?: never; '#dbRef'?: never }' and
    { [P in NonFunctionPropertyNames<T>]?: DeepPartialGraph<T[P]> } in the target

  • R ('Object') -> R ('Intersection|IncludesNonWideningType')

  • P (Object) -> P (Intersection|IncludesNonWideningType)

  • R -> P again

I've not been able to replicate this in a baseline yet

Current baseline WIP
// R -> Model
// 

// @target: ES2019
// @strict

export interface Constructor {
new (...args: any[]): M;
}

export interface QueryBuilderYieldingOne extends QueryBuilder<QM, QM, QM> {}

export interface QueryBuilderYieldingOneOrNone extends QueryBuilder<QM, QM, QM | undefined> {}

export interface QueryBuilderYieldingCount<QM extends Model, RM = QM[]>
extends QueryBuilderBase<QM, RM, number>,
Executable {
throwIfNotFound(): this;
}

type Value =
| string
| number
| boolean
| Date
| string[]
| number[]
| boolean[]
| Date[]
| null
| Raw
| Literal;

export interface Page {
total: number;
results: QM[];
}

interface BluebirdMapper<T, Result> {
(item: T, index: number): Result;
}

interface NodeStyleCallback {
(err: any, result?: any): void;
}

interface InsertGraph {
(modelsOrObjects?: DeepPartialGraph[], options?: InsertGraphOptions): QueryBuilder<QM, QM[]>;
(modelOrObject?: DeepPartialGraph, options?: InsertGraphOptions): QueryBuilder<QM, QM>;
(): this;
}

interface QueryBuilderBase<QM extends Model, RM, RV> extends QueryInterface<QM, RM, RV> {
modify(func: (builder: this) => void): this;
modify(namedFilter: string): this;

applyFilter(...namedFilters: string[]): this;

// findById(id: Id): QueryBuilderYieldingOneOrNone;
// findById(idOrIds: IdOrIds): this;
// findByIds(ids: Id[] | Id[][]): this;
// /** findOne is shorthand for .where(...whereArgs).first() */
// findOne: FindOne;

// insert: Insert;
// insertAndFetch(modelOrObject: Partial): QueryBuilder<QM, QM>;
// insertAndFetch(modelsOrObjects?: Partial[]): QueryBuilder<QM, QM[]>;

insertGraph: InsertGraph;
// insertGraphAndFetch: InsertGraphAndFetch;

// /**
// * insertWithRelated is an alias for insertGraph.
// */
// insertWithRelated: InsertGraph;
// insertWithRelatedAndFetch: InsertGraphAndFetch;

// /**
// * @return a Promise of the number of updated rows
// */
// update(modelOrObject: PartialUpdate): QueryBuilderYieldingCount<QM, RM>;
// updateAndFetch(modelOrObject: PartialUpdate): QueryBuilder<QM, QM>;
// updateAndFetchById(id: Id, modelOrObject: PartialUpdate): QueryBuilder<QM, QM>;

// /**
// * @return a Promise of the number of patched rows
// */
// patch(modelOrObject: PartialUpdate): QueryBuilderYieldingCount<QM, RM>;
// patchAndFetchById(idOrIds: IdOrIds, modelOrObject: PartialUpdate): QueryBuilder<QM, QM>;
// patchAndFetch(modelOrObject: PartialUpdate): QueryBuilder<QM, QM>;

// upsertGraph: UpsertGraph;
// upsertGraphAndFetch: UpsertGraphAndFetch;

// /**
// * @return a Promise of the number of deleted rows
// */
// deleteById(idOrIds: IdOrIds): QueryBuilderYieldingCount<QM, RM>;

// relate(ids: IdOrIds | Partial | Partial[]): this;
// unrelate(): this;

// forUpdate(): this;
// forShare(): this;

// // TODO: fromJS does not exist in current knex documentation: http://knexjs.org/#Builder-fromJS
// withSchema(schemaName: string): this;

// joinRelation: JoinRelation;
// innerJoinRelation: JoinRelation;
// outerJoinRelation: JoinRelation;
// leftJoinRelation: JoinRelation;
// leftOuterJoinRelation: JoinRelation;
// rightJoinRelation: JoinRelation;
// rightOuterJoinRelation: JoinRelation;
// fullOuterJoinRelation: JoinRelation;

// // TODO: avgDistinct does not exist in current knex documentation: http://knexjs.org/#Builder-fromJS
// // TODO: modify does not exist in current knex documentation: http://knexjs.org/#Builder-modify

// // TODO: the return value of this method matches the knex typescript and documentation.
// // The Objection documentation incorrectly states this returns a QueryBuilder.
// columnInfo(column?: string): Promise<knex.ColumnInfo>;

// whereComposite(column: ColumnRef, value: Value | QueryBuilder<any, any[]>): this;
// whereComposite(column: ColumnRef[], value: Value[] | QueryBuilder<any, any[]>): this;
// whereComposite(
// column: ColumnRef,
// operator: string,
// value: Value | QueryBuilder<any, any[]>
// ): this;
// whereComposite(
// column: ColumnRef[],
// operator: string,
// value: Value[] | QueryBuilder<any, any[]>
// ): this;
// whereInComposite(column: ColumnRef | ColumnRef[], values: Value[] | QueryBuilder<any, any[]>): this;

// whereJsonSupersetOf: WhereJson<QM, RM, RV>;
// orWhereJsonSupersetOf: WhereJson<QM, RM, RV>;

// whereJsonNotSupersetOf: WhereJson<QM, RM, RV>;
// orWhereJsonNotSupersetOf: WhereJson<QM, RM, RV>;

// whereJsonSubsetOf: WhereJson<QM, RM, RV>;
// orWhereJsonSubsetOf: WhereJson<QM, RM, RV>;

// whereJsonNotSubsetOf: WhereJson<QM, RM, RV>;
// orWhereJsonNotSubsetOf: WhereJson<QM, RM, RV>;

// whereJsonIsArray: WhereFieldExpression<QM, RM, RV>;
// orWhereJsonIsArray: WhereFieldExpression<QM, RM, RV>;

// whereJsonNotArray: WhereFieldExpression<QM, RM, RV>;
// orWhereJsonNotArray: WhereFieldExpression<QM, RM, RV>;

// whereJsonIsObject: WhereFieldExpression<QM, RM, RV>;
// orWhereJsonIsObject: WhereFieldExpression<QM, RM, RV>;

// whereJsonNotObject: WhereFieldExpression<QM, RM, RV>;
// orWhereJsonNotObject: WhereFieldExpression<QM, RM, RV>;

// whereJsonHasAny: WhereJsonExpression<QM, RM, RV>;
// orWhereJsonHasAny: WhereJsonExpression<QM, RM, RV>;

// whereJsonHasAll: WhereJsonExpression<QM, RM, RV>;
// orWhereJsonHasAll: WhereJsonExpression<QM, RM, RV>;

// Non-query methods:

context(queryContext: object): this;
context(): QueryContext;
mergeContext(queryContext: object): this;

reject(reason: any): this;
resolve(value: any): this;

isExecutable(): boolean;
isFind(): boolean;
isInsert(): boolean;
isUpdate(): boolean;
isDelete(): boolean;
isRelate(): boolean;
isUnrelate(): boolean;
hasWheres(): boolean;
hasSelects(): boolean;
hasEager(): boolean;

runBefore(fn: (result: any, builder: QueryBuilder<QM, any>) => any): this;
runAfter(fn: (result: any, builder: QueryBuilder<QM, any>) => any): this;
onBuild(fn: (builder: this) => void): this;
//onBuildKnex(fn: (knexBuilder: knex.QueryBuilder, builder: this) => void): this;
onError(fn: (error: Error, builder: this) => any): this;

//eagerAlgorithm(algo: EagerAlgorithm): this;
// eagerOptions(opts: EagerOptions): this;

// eager(relationExpression: RelationExpression, filters?: FilterExpression): this;
// mergeEager(relationExpression: RelationExpression, filters?: FilterExpression): this;

// joinEager(relationExpression: RelationExpression, filters?: FilterExpression): this;
// mergeJoinEager(relationExpression: RelationExpression, filters?: FilterExpression): this;

// naiveEager(relationExpression: RelationExpression, filters?: FilterExpression): this;
// mergeNaiveEager(relationExpression: RelationExpression, filters?: FilterExpression): this;

// allowEager: RelationExpressionMethod<QM, RM, RV>;
// modifyEager: ModifyEager<QM, RM, RV>;
// filterEager: ModifyEager<QM, RM, RV>;

// allowInsert: RelationExpressionMethod<QM, RM, RV>;
// allowUpsert: RelationExpressionMethod<QM, RM, RV>;

modelClass(): typeof Model;

toString(): string;

toSql(): string;

skipUndefined(): this;

transacting(transaction: Transaction): this;

clone(): this;

// We get then and catch by extending Promise

map<V, Result>(mapper: BluebirdMapper<V, Result>): Promise<Result[]>;
return(returnValue: V): Promise;
bind(context: any): Promise;
reflect(): Promise;

asCallback(callback: NodeStyleCallback): Promise;

nodeify(callback: NodeStyleCallback): Promise;

resultSize(): Promise;

page(page: number, pageSize: number): QueryBuilder<QM, Page>;
// range(): QueryBuilder<QM, Page>;
range(start: number, end: number): QueryBuilder<QM, Page>;
pluck(propertyName: string): this;
// first(): QueryBuilderYieldingOneOrNone;

// alias(alias: string): this;
// aliasFor(modelClassOrTableName: string | ModelClass, alias:string): this;
// tableRefFor(modelClass: ModelClass): string;
// tableNameFor(modelClass: ModelClass): string;

//traverse(modelClass: typeof Model, traverser: TraverserFunction): this;

pick(modelClass: typeof Model, properties: string[]): this;
pick(properties: string[]): this;

omit(modelClass: typeof Model, properties: string[]): this;
omit(properties: string[]): this;

returning(columns: string | string[]): QueryBuilder<QM, RM>;

// timeout(ms: number, options?: TimeoutOptions): this;
}

export interface Executable extends Promise {
execute(): Promise;
}

export interface QueryBuilder<QM extends Model, RM = QM[], RV = RM>
extends QueryBuilderBase<QM, RM, RV>,
Executable {
throwIfNotFound(): QueryBuilder<QM, RM>;
castTo(model: T): QueryBuilder<QM, InstanceType[]>;
}

interface Transaction {
savepoint(transactionScope: (trx: Transaction) => any): Promise;
commit(value?: any): Promise;
rollback(error?: Error): Promise;
}

interface QueryInterface<QM extends Model, RM, RV> {
select: Select<QM, RM, RV>;
as: As<QM, RM, RV>;
columns: Select<QM, RM, RV>;
column: Select<QM, RM, RV>;
from: Table<QM, RM, RV>;
into: Table<QM, RM, RV>;
table: Table<QM, RM, RV>;
distinct: Distinct<QM, RM, RV>;

// Joins
join: Join<QM, RM, RV>;
joinRaw: JoinRaw<QM, RM, RV>;
innerJoin: Join<QM, RM, RV>;
leftJoin: Join<QM, RM, RV>;
leftOuterJoin: Join<QM, RM, RV>;
rightJoin: Join<QM, RM, RV>;
rightOuterJoin: Join<QM, RM, RV>;
outerJoin: Join<QM, RM, RV>;
fullOuterJoin: Join<QM, RM, RV>;
crossJoin: Join<QM, RM, RV>;

// Withs
with: With<QM, RM, RV>;
withRaw: WithRaw<QM, RM, RV>;
withWrapped: WithWrapped<QM, RM, RV>;

// Wheres
where: Where<QM, RM, RV>;
andWhere: Where<QM, RM, RV>;
orWhere: Where<QM, RM, RV>;
whereNot: Where<QM, RM, RV>;
andWhereNot: Where<QM, RM, RV>;
orWhereNot: Where<QM, RM, RV>;
whereRaw: WhereRaw<QM, RM, RV>;
orWhereRaw: WhereRaw<QM, RM, RV>;
andWhereRaw: WhereRaw<QM, RM, RV>;
whereWrapped: WhereWrapped<QM, RM, RV>;
havingWrapped: WhereWrapped<QM, RM, RV>;
whereExists: WhereExists<QM, RM, RV>;
orWhereExists: WhereExists<QM, RM, RV>;
whereNotExists: WhereExists<QM, RM, RV>;
orWhereNotExists: WhereExists<QM, RM, RV>;
whereIn: WhereIn<QM, RM, RV>;
orWhereIn: WhereIn<QM, RM, RV>;
whereNotIn: WhereIn<QM, RM, RV>;
orWhereNotIn: WhereIn<QM, RM, RV>;
whereNull: WhereNull<QM, RM, RV>;
orWhereNull: WhereNull<QM, RM, RV>;
whereNotNull: WhereNull<QM, RM, RV>;
orWhereNotNull: WhereNull<QM, RM, RV>;
whereBetween: WhereBetween<QM, RM, RV>;
orWhereBetween: WhereBetween<QM, RM, RV>;
andWhereBetween: WhereBetween<QM, RM, RV>;
whereNotBetween: WhereBetween<QM, RM, RV>;
orWhereNotBetween: WhereBetween<QM, RM, RV>;
andWhereNotBetween: WhereBetween<QM, RM, RV>;
whereColumn: Where<QM, RM, RV>;
andWhereColumn: Where<QM, RM, RV>;
orWhereColumn: Where<QM, RM, RV>;
whereNotColumn: Where<QM, RM, RV>;
andWhereNotColumn: Where<QM, RM, RV>;
orWhereNotColumn: Where<QM, RM, RV>;

// Group by
groupBy: GroupBy<QM, RM, RV>;
groupByRaw: RawMethod<QM, RM, RV>;

// Order by
orderBy: OrderBy<QM, RM, RV>;
orderByRaw: RawMethod<QM, RM, RV>;

// Union
union: SetOperations;
unionAll: SetOperations;
intersect: SetOperations;

// Having
having: Where<QM, RM, RV>;
andHaving: Where<QM, RM, RV>;
orHaving: Where<QM, RM, RV>;
havingRaw: WhereRaw<QM, RM, RV>;
orHavingRaw: WhereRaw<QM, RM, RV>;
havingIn: WhereIn<QM, RM, RV>;
orHavingIn: WhereIn<QM, RM, RV>;
havingNotIn: WhereIn<QM, RM, RV>;
orHavingNotIn: WhereIn<QM, RM, RV>;
havingNull: WhereNull<QM, RM, RV>;
orHavingNull: WhereNull<QM, RM, RV>;
havingNotNull: WhereNull<QM, RM, RV>;
orHavingNotNull: WhereNull<QM, RM, RV>;
havingExists: WhereExists<QM, RM, RV>;
orHavingExists: WhereExists<QM, RM, RV>;
havingNotExists: WhereExists<QM, RM, RV>;
orHavingNotExists: WhereExists<QM, RM, RV>;
havingBetween: WhereBetween<QM, RM, RV>;
orHavingBetween: WhereBetween<QM, RM, RV>;
havingNotBetween: WhereBetween<QM, RM, RV>;
orHavingNotBetween: WhereBetween<QM, RM, RV>;

// Clear
clearSelect(): this;
clearOrder(): this;
clearWhere(): this;

// Paging
offset(offset: number): this;
limit(limit: number): this;

// Aggregation
count(columnName?: string): this;
countDistinct(columnName?: string): this;
min(columnName: string): this;
max(columnName: string): this;
sum(columnName: string): this;
sumDistinct(columnName: string): this;
avg(columnName: string): this;
avgDistinct(columnName: string): this;
increment(columnName: string, amount?: number): this;
decrement(columnName: string, amount?: number): this;

debug(enabled?: boolean): this;
pluck(column: string): this;

//del(): QueryBuilderYieldingCount<QM, RM>;
//delete(): QueryBuilderYieldingCount<QM, RM>;
truncate(): this;

transacting(trx: Transaction): this;
connection(connection: any): this;

clone(): this;
}

interface Castable {
castText(): this;
castInt(): this;
castBigInt(): this;
castFloat(): this;
castDecimal(): this;
castReal(): this;
castBool(): this;
castJson(): this;
castArray(): this;
asArray(): this;
castType(sqlType: string): this;
castTo(sqlType: string): this;
as(alias: string): this;
}

export interface Literal extends Castable {}

export interface Reference extends Castable {}

type Raw = any;

interface As<QM extends Model, RM, RV> {
(alias: string): QueryBuilder<QM, RM, RV>;
}

interface Select<QM extends Model, RM, RV> extends ColumnNamesMethod<QM, RM, RV> {}

interface Table<QM extends Model, RM, RV> {
(tableName: TableName): QueryBuilder<QM, RM, RV>;
(callback: (this: QueryBuilder<QM, QM[]>, queryBuilder: QueryBuilder<QM, QM[]>) => void): QueryBuilder<QM, RM, RV>;
}

interface Distinct<QM extends Model, RM, RV> extends ColumnNamesMethod<QM, RM, RV> {}

interface Join<QM extends Model, RM, RV> {
(raw: Raw): QueryBuilder<QM, RM, RV>;
(tableName: TableName, clause: (this: any, join: any) => void): QueryBuilder<QM, RM, RV>;
(tableName: TableName, columns: { [key: string]: string | number | Raw | Reference }): QueryBuilder<QM, RM, RV>;
(tableName: TableName, raw: Raw): QueryBuilder<QM, RM, RV>;
(tableName: TableName, column1: ColumnRef, column2: ColumnRef): QueryBuilder<QM, RM, RV>;
(tableName: TableName, column1: ColumnRef, operator: string, column2: ColumnRef): QueryBuilder<QM, RM, RV>;
(queryBuilder: QueryBuilder): QueryBuilder<QM, RM, RV>;
}

type ColumnRef = string | Raw | Reference | QueryBuilder<any, any[]>;
type TableName = string | Raw | Reference | QueryBuilder<any, any[]>;

interface JoinRaw<QM extends Model, RM, RV> {
(sql: string, bindings?: any): QueryBuilder<QM, RM, RV>;
}

interface With<QM extends Model, RM, RV> extends WithRaw<QM, RM, RV>, WithWrapped<QM, RM, RV> {}

interface WithRaw<QM extends Model, RM, RV> {
(alias: string, raw: Raw): QueryBuilder<QM, RM, RV>;
// join: knex.JoinClause;
(alias: string, sql: string, bindings?: any): QueryBuilder<QM, RM, RV>;
}

interface WithWrapped<QM extends Model, RM, RV> {
(alias: string, callback: (queryBuilder: QueryBuilder<QM, QM[]>) => any): QueryBuilder<QM, RM, RV>;
}

interface Where<QM extends Model, RM, RV> extends WhereRaw<QM, RM, RV> {
(callback: (this: QueryBuilder<QM, QM[]>, queryBuilder: QueryBuilder<QM, QM[]>) => void): QueryBuilder<QM, RM, RV>;
(object: object): QueryBuilder<QM, RM, RV>;
(column: keyof QM | ColumnRef, value: Value | Reference | QueryBuilder<any, any[]>): QueryBuilder<QM, RM, RV>;
(column: keyof QM | ColumnRef, operator: string, value: Value | Reference | QueryBuilder<any, any[]>): QueryBuilder<
QM,
RM,
RV

;
(
column: ColumnRef,
callback: (this: QueryBuilder<QM, QM[]>, queryBuilder: QueryBuilder<QM, QM[]>) => void
): QueryBuilder<QM, RM, RV>;
}

interface FindOne {
(condition: boolean): QueryBuilderYieldingOneOrNone;
(
callback: (this: QueryBuilder<QM, QM[]>, queryBuilder: QueryBuilder<QM, QM[]>) => void
): QueryBuilderYieldingOneOrNone;
(object: object): QueryBuilderYieldingOneOrNone;
(sql: string, ...bindings: any[]): QueryBuilderYieldingOneOrNone;
(sql: string, bindings: any): QueryBuilderYieldingOneOrNone;
(column: ColumnRef, value: Value | Reference | QueryBuilder<any, any[]>): QueryBuilderYieldingOneOrNone;
(
column: ColumnRef,
operator: string,
value: Value | Reference | QueryBuilder<any, any[]>
): QueryBuilderYieldingOneOrNone;
(
column: ColumnRef,
callback: (this: QueryBuilder<QM, QM[]>, queryBuilder: QueryBuilder<QM, QM[]>) => void
): QueryBuilderYieldingOneOrNone;
}

interface WhereRaw<QM extends Model, RM, RV> extends RawMethod<QM, RM, RV> {
(condition: boolean): QueryBuilder<QM, RM, RV>;
}

interface WhereWrapped<QM extends Model, RM, RV> {
(callback: (queryBuilder: QueryBuilder<QM, QM[]>) => void): QueryBuilder<QM, RM, RV>;
}

interface WhereNull<QM extends Model, RM, RV> {
(column: ColumnRef): QueryBuilder<QM, RM, RV>;
}

interface WhereIn<QM extends Model, RM, RV> {
(column: ColumnRef | ColumnRef[], values: Value[]): QueryBuilder<QM, RM, RV>;
(
column: ColumnRef | ColumnRef[],
callback: (this: QueryBuilder<QM, QM[]>, queryBuilder: QueryBuilder<QM, QM[]>) => void
): QueryBuilder<QM, RM, RV>;
(column: ColumnRef | ColumnRef[], query: QueryBuilder<any, any[]>): QueryBuilder<QM, RM, RV>;
}

interface WhereBetween<QM extends Model, RM, RV> {
(column: ColumnRef, range: [Value, Value]): QueryBuilder<QM, RM, RV>;
}

interface WhereExists<QM extends Model, RM, RV> {
(callback: (this: QueryBuilder<QM, QM[]>, queryBuilder: QueryBuilder<QM, QM[]>) => void): QueryBuilder<QM, RM, RV>;
(query: QueryBuilder<any, any[]>): QueryBuilder<QM, RM, RV>;
(raw: Raw): QueryBuilder<QM, RM, RV>;
}

interface GroupBy<QM extends Model, RM, RV> extends RawMethod<QM, RM, RV>, ColumnNamesMethod<QM, RM, RV> {}

interface OrderBy<QM extends Model, RM, RV> {
(column: ColumnRef, direction?: string): QueryBuilder<QM, RM, RV>;
}

interface SetOperations {
(
callback: (this: QueryBuilder<QM, QM[]>, queryBuilder: QueryBuilder<QM, QM[]>) => void,
wrap?: boolean
): QueryBuilder<QM, QM[]>;
(
callbacks: ((this: QueryBuilder<QM, QM[]>, queryBuilder: QueryBuilder<QM, QM[]>) => void)[],
wrap?: boolean
): QueryBuilder<QM, QM[]>;
(...callbacks: ((this: QueryBuilder<QM, QM[]>, queryBuilder: QueryBuilder<QM, QM[]>) => void)[]): QueryBuilder<
QM,
QM[]

;
}

// commons

interface ColumnNamesMethod<QM extends Model, RM, RV> {
(...columnNames: ColumnRef[]): QueryBuilder<QM, RM, RV>;
(columnNames: ColumnRef[]): QueryBuilder<QM, RM, RV>;
}

interface RawMethod<QM extends Model, RM, RV> {
(sql: string, ...bindings: any[]): QueryBuilder<QM, RM, RV>;
(sql: string, bindings: any): QueryBuilder<QM, RM, RV>;
(raw: Raw): QueryBuilder<QM, RM, RV>;
}

// TS 2.5 doesn't support interfaces with static methods or fields, so
// this must be declared as a class:
declare class Model {
static tableName: string;
static jsonSchema: any;
static idColumn: string | string[];
static modelPaths: string[];
static relationMappings: any;
static jsonAttributes: string[];
static virtualAttributes: string[];
static uidProp: string;
static uidRefProp: string;
static dbRefProp: string;
static propRefRegex: RegExp;
static pickJsonSchemaProperties: boolean;
static defaultEagerAlgorithm?: any;
static defaultEagerOptions?: any;
// static QueryBuilder: typeof QueryBuilder;
static columnNameMappers: any;
static relatedFindQueryMutates: boolean;
static relatedInsertQueryMutates: boolean;
static modifiers: any;

// static raw: knex.RawBuilder;
// static fn: knex.FunctionHelper;

// static BelongsToOneRelation: Relation;
// static HasOneRelation: Relation;
// static HasManyRelation: Relation;
// static ManyToManyRelation: Relation;
// static HasOneThroughRelation: Relation;

// static JoinEagerAlgorithm: EagerAlgorithm;
// static WhereInEagerAlgorithm: EagerAlgorithm;
// static NaiveEagerAlgorithm: EagerAlgorithm;

// static getRelations(): { [key: string]: Relation };

static query(
this: Constructor,
trxOrKnex?: Transaction // | knex
): QueryBuilder;

// // This can only be used as a subquery so the result model type is irrelevant.
// static relatedQuery(relationName: string): QueryBuilder<any, any[]>;
// static knex(knex?: knex): knex;
// static knexQuery(): knex.QueryBuilder;
// static bindKnex(this: M, knex: knex): M;
// static bindTransaction(this: M, transaction: Transaction): M;
// static createValidator(): Validator;
// static createValidationError(args: CreateValidationErrorArgs): Error;
// static createNotFoundError(): Error;

// // fromJson and fromDatabaseJson both return an instance of Model, not a Model class:
// static fromJson(this: Constructor, json: Pojo, opt?: ModelOptions): M;
// static fromDatabaseJson(this: Constructor, row: Pojo): M;

// static omitImpl(f: (obj: object, prop: string) => void): void;

// // loadRelated is overloaded to support both Model and Model[] variants:
// static loadRelated(
// this: Constructor,
// models: QM[],
// expression: RelationExpression,
// filters?: Filters,
// trxOrKnex?: Transaction | knex
// ): QueryBuilder;

// static loadRelated(
// this: Constructor,
// model: QM,
// expression: RelationExpression,
// filters?: Filters,
// trxOrKnex?: Transaction | knex
// ): QueryBuilderYieldingOne;

// static traverse(
// filterConstructor: typeof Model,
// models: Model | Model[],
// traverser: TraverserFunction
// ): void;

// static traverse(models: Model | Model[], traverser: TraverserFunction): void;

// static tableMetadata(opt?: TableMetadataOptions): TableMetadata;
// static fetchTableMetadata(opt?: FetchTableMetadataOptions): Promise;
// // Implementation note: At least as of TypeScript 2.7, subclasses of
// // methods that return this are not compatible with their superclass.
// // For example, class Movie extends Model could not be passed as a
// // "Model" to a function, because the methods that return this return
// // Movie, and not Model. The foo<M>(this: M, ... is a workaround.

// $id(): any;
// $id(id: any): void;

// $beforeValidate(jsonSchema: JsonSchema, json: Pojo, opt: ModelOptions): JsonSchema;
// $validate(json: Pojo, opt: ModelOptions): Pojo; // may throw ValidationError if validation fails
// $afterValidate(json: Pojo, opt: ModelOptions): void; // may throw ValidationError if validation fails

// $toDatabaseJson(): object;
// $toJson(opt?: ToJsonOptions): object;
// toJSON(opt?: ToJsonOptions): object;
// $parseDatabaseJson(json: Pojo): Pojo;
// $formatDatabaseJson(json: Pojo): Pojo;
// $parseJson(json: Pojo, opt?: ModelOptions): Pojo;
// $formatJson(json: Pojo): Pojo;
// $setJson(this: T, json: Pojo, opt?: ModelOptions): T;
// $setDatabaseJson(this: M, json: Pojo): M;
// $setRelated<T, RelatedM extends Model>(
// this: T,
// relation: String | Relation,
// related: RelatedM | RelatedM[] | null | undefined
// ): T;
// $appendRelated<T, RelatedM extends Model>(
// this: T,
// relation: String | Relation,
// related: RelatedM | RelatedM[] | null | undefined
// ): T;

// $set(this: T, obj: Pojo): T;
// $omit(this: T, keys: string | string[] | Properties): T;
// $pick(this: T, keys: string | string[] | Properties): T;
// $clone(this: T, opt?: CloneOptions): T;

// $query(this: QM, trxOrKnex?: Transaction | knex): QueryBuilder<QM, QM>;

// /**
// * If you add fields to your model, you get $relatedQuery typings for
// * free.
// *
// * Note that if you make any chained calls to the QueryBuilder,
// * though, you should apply a cast, which will make your code use not this
// * signatue, but the following signature.
// */
// $relatedQuery<K extends keyof this, V extends this[K] & Model>(
// relationName: K,
// trxOrKnex?: Transaction | knex
// ): QueryBuilder<V, V, V>;

// /**
// * Builds a query that only affects the models related to this instance
// * through a relation. Note that this signature requires a
// * type cast (like bob.$relatedQuery<Animal>('pets')).
// */
// $relatedQuery<QM extends Model, RM = QM[]>(
// relationName: keyof this | string,
// trxOrKnex?: Transaction | knex
// ): QueryBuilder<QM, RM>;

// $loadRelated(
// this: QM,
// expression: keyof this | RelationExpression,
// filters?: Filters,
// trxOrKnex?: Transaction | knex
// ): QueryBuilder<QM, QM>;

// $traverse(traverser: TraverserFunction): void;
// $traverse(filterConstructor: this, traverser: TraverserFunction): void;

// $knex(): knex;
// $transaction(): knex;

// $beforeInsert(queryContext: QueryContext): Promise | void;
// $afterInsert(queryContext: QueryContext): Promise | void;
// $afterUpdate(opt: ModelOptions, queryContext: QueryContext): Promise | void;
// $beforeUpdate(opt: ModelOptions, queryContext: QueryContext): Promise | void;
// $afterGet(queryContext: QueryContext): Promise | void;
// $beforeDelete(queryContext: QueryContext): Promise | void;
// $afterDelete(queryContext: QueryContext): Promise | void;
}

type GraphModel =
| ({ "#id"?: string; "#ref"?: never; "#dbRef"?: never } & T)
| ({ "#id"?: never; "#ref": string; "#dbRef"?: never } & { [P in keyof T]?: never })
| ({ "#id"?: never; "#ref"?: never; "#dbRef": number } & { [P in keyof T]?: never });

type NonFunctionPropertyNames = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];

interface DeepPartialGraphArray extends Array<DeepPartialGraph> {}

type DeepPartialGraphModel =
| GraphModel<{ [P in NonFunctionPropertyNames]?: DeepPartialGraph<T[P]> }>
| Partial;

type DeepPartialGraph = T extends any[] | ReadonlyArray
? DeepPartialGraphArray<T[number]>
: T extends Model
? DeepPartialGraphModel
: T;

export interface InsertGraphOptions {
relate?: boolean | string[];
}

export interface QueryContext {
// transaction: Transaction;
[key: string]: any;
}

export interface TableMetadata {
columns: Array;
}

export interface TableMetadataOptions {
table: string;
}

export interface FetchTableMetadataOptions {
// knex?: knex;
force?: boolean;
table?: string;
}

class R extends Model {
p?: P; // comment and it works
d!: D;
}

export class P extends Model {
// remove extends and things work
rig!: R; // comment this line and things work
}

export class D extends Model {
// remove extends Model and things work
rs!: R[]; // comment out this line and things work
}

async function main() {
const d = new D();
await R.query().insertGraph({
d: d // change this to just be d and problem goes away!
});
}

main().then(() => {
console.log("here");
});

@orta
Copy link
Contributor

orta commented Apr 14, 2020

Cool, started re-looking at this and it looks like both the two repros in this issue by @koskimas are now fixed on master - It looks like the PR which fixed it is probably #37589

@orta orta closed this as completed Apr 14, 2020
@lookfirst
Copy link
Author

@orta I used the included test project and confirmed it no longer errors in Version 3.9.0-dev.20200414

@xtance
Copy link

xtance commented Nov 16, 2021

Still "Maximum call stack size exceeded" with objection

@koskimas
Copy link

koskimas commented Nov 16, 2021

@xtance It was fine for a long time there. It's a new issue, not necessarily related to this one. Adding strict: true to tsconfig seems to fix the new issue. If you want to help, and someone to help you, please provide a reproduction.

@pgcalixto
Copy link

I also got the "Maximum call stack size exceeded" using Objection on version 3.0.0 and Typescript on version 4.4.4.
I'll try to provide a reproduction later.

@michalmokros
Copy link

Same error occured to me, I tried updating typescript to 4.5.2 but it didn't help. My objection version is 3.0.0 and knex version is 0.95.14

@DeznekCZ
Copy link

This error is produced also in latest build. I reach it finally too. I thing its about toobig projects. But where is the limit?

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
Projects
None yet
Development

No branches or pull requests

10 participants