GoogleFeud / ts-runtime-checks

A typescript transformer that automatically generates validation code from your types.
https://googlefeud.github.io/ts-runtime-checks/
MIT License
312 stars 7 forks source link

[BUG] Recursive call within a class member function fails with "RangeError: Maximum call stack size exceeded" #25

Closed FredTreg closed 1 year ago

FredTreg commented 1 year ago

Describe the bug When running tspc on my project, I get the following error:

evalmachine.<anonymous>:118053
              throw e;
              ^

RangeError: Maximum call stack size exceeded
    at Object.visitEachChild (/workspace/coucou/server/node_modules/typescript/lib/typescript.js:86166:26)
    at Transformer.visitor (/workspace/coucou/server/node_modules/ts-runtime-checks/dist/transformer.js:199:37)
    at /workspace/coucou/server/node_modules/ts-runtime-checks/dist/transformer.js:199:73
    at visitArrayWorker (/workspace/coucou/server/node_modules/typescript/lib/typescript.js:85983:51)
    at visitNodes2 (/workspace/coucou/server/node_modules/typescript/lib/typescript.js:85954:21)
    at visitEachChildOfMethodSignature (/workspace/coucou/server/node_modules/typescript/lib/typescript.js:86250:13)
    at Object.visitEachChild (/workspace/coucou/server/node_modules/typescript/lib/typescript.js:86171:35)
    at Transformer.visitor (/workspace/coucou/server/node_modules/ts-runtime-checks/dist/transformer.js:199:37)
    at Transformer.visitor (/workspace/coucou/server/node_modules/ts-runtime-checks/dist/transformer.js:114:26)
    at /workspace/coucou/server/node_modules/ts-runtime-checks/dist/transformer.js:199:73

Playground link I spend a few hours trying to isolate the error with no luck. Rather than trying more, I've decided to file the bug and request help: do you know of a way to isolate what piece of code is being transformed? any magic "verbose" option? If not I'll investigate more...

Expected behavior Compiler does not fail

Additional context

GoogleFeud commented 1 year ago

Do you have an intersection of a utility type and check type(s) anywhere?

interface Example {
  thing: NoCheck<string> & MinLen<3> & MaxLen<10>
}

There's an issue when chaining check types with other utility types that I fixed earlier today.

FredTreg commented 1 year ago

I do not think so. I tried to rebuild the Str utility though as I used it massively across the project:

export type Str<
  minLen extends number | undefined = undefined,
  maxLen extends number | undefined = undefined,
  matches extends string | undefined = undefined
> = string &
  (minLen extends number ? MinLen<minLen> : string) &
  (maxLen extends number ? MaxLen<maxLen> : string) &
  (matches extends string ? Matches<matches> : string)

but is it ok when used in the playground.

I'll try with your next release once you release it.

GoogleFeud commented 1 year ago

Can you go to node_modules/ts-runtime-checks/dist/transformer.js and replace line 113/114 with:

if (!this.validatedDecls.has(decl) && (ts.isFunctionExpression(decl) || ts.isFunctionDeclaration(decl) || ts.isArrowFunction(decl))) this.visitor(decl, body);

and see if that fixes the issue.

FredTreg commented 1 year ago

That did fix things (ts = typescript_1.default) My whole project compiles :)

GoogleFeud commented 1 year ago

Could you please add the following code below the if statement to identify the specific node causing the issue?

else {
   console.log(decl.kind, decl.pos !== -1 ? decl.getText() : "no text representation");
}
FredTreg commented 1 year ago

Not sure it helps, it displays thousands of lines such as:

173 $on<V extends U>(eventType: V, callback: (event: V extends 'query' ? Prisma.QueryEvent : Prisma.LogEvent) => void): void;
172 toLowerCase(): string;
172 replace(searchValue: { [Symbol.replace](string: string, replaceValue: string): string; }, replaceValue: string): string;
172 replace(searchValue: { [Symbol.replace](string: string, replaceValue: string): string; }, replaceValue: string): string;
172 normalize(form: "NFC" | "NFD" | "NFKC" | "NFKD"): string;
172 lastIndexOf(searchString: string, position?: number): number;
172 substring(start: number, end?: number): string;
172 match(matcher: { [Symbol.match](string: string): RegExpMatchArray | null; }): RegExpMatchArray | null;
172 match(matcher: { [Symbol.match](string: string): RegExpMatchArray | null; }): RegExpMatchArray | null;
172 lastIndexOf(searchString: string, position?: number): number;
172 substring(start: number, end?: number): string;
172 replace(searchValue: { [Symbol.replace](string: string, replaceValue: string): string; }, replaceValue: string): string;
172 match(matcher: { [Symbol.match](string: string): RegExpMatchArray | null; }): RegExpMatchArray | null;
172 match(matcher: { [Symbol.match](string: string): RegExpMatchArray | null; }): RegExpMatchArray | null;
172 match(matcher: { [Symbol.match](string: string): RegExpMatchArray | null; }): RegExpMatchArray | null;
172 replace(searchValue: { [Symbol.replace](string: string, replaceValue: string): string; }, replaceValue: string): string;
172 replace(searchValue: { [Symbol.replace](string: string, replacer: (substring: string, ...args: any[]) => string): string; }, replacer: (substring: string, ...args: any[]) => string): string;
172 toLowerCase(): string;
172 replace(searchValue: { [Symbol.replace](string: string, replacer: (substring: string, ...args: any[]) => string): string; }, replacer: (substring: string, ...args: any[]) => string): string;
172 toLowerCase(): string;
172 toLowerCase(): string;
172 lastIndexOf(searchString: string, position?: number): number;
172 substring(start: number, end?: number): string;
172 indexOf(searchString: string, position?: number): number;
172 substring(start: number, end?: number): string;
172 replace(searchValue: { [Symbol.replace](string: string, replaceValue: string): string; }, replaceValue: string): string;
172 toLowerCase(): string;
172 replace(searchValue: { [Symbol.replace](string: string, replaceValue: string): string; }, replaceValue: string): string;
...
FredTreg commented 1 year ago

But your code gave me an idea on where to place the console.log which lead to the following playground in error:

playground

(Recursive member function call)

GoogleFeud commented 1 year ago

Yup that explains everything, next patch will be released by the end of the week!