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

Max depth limit does not trigger. Gives up and resolves type to any #32707

Open
AnyhowStep opened this issue Aug 5, 2019 · 18 comments
Open

Max depth limit does not trigger. Gives up and resolves type to any #32707

AnyhowStep opened this issue Aug 5, 2019 · 18 comments
Labels
Bug A bug in TypeScript
Milestone

Comments

@AnyhowStep
Copy link
Contributor

AnyhowStep commented Aug 5, 2019

TypeScript Version: 3.5.1

Search Terms:

  • Type instantiation is excessively deep and possibly infinite.ts(2589)
  • Chaining methods
  • Fluent API
  • Generic class
  • Generic method

Code

Using DataT,

type AppendToArray<ArrT extends any[], T> = (
    (
        ArrT[number]|
        T
    )[]
);
interface Data {
    arr : number[]
};
type AppendToFoo<C extends Data, N extends number> = (
    Foo<{arr:AppendToArray<C["arr"], N>}>
);

class Foo<DataT extends Data> {
    arr! : DataT["arr"];
    //Using `DataT`
    x<N extends number> (n:N) : AppendToFoo<DataT, N> {
        return null!;
    }
}
declare const foo0 : Foo<{arr:0[]}>;
/*
const foo12: Foo<{
    arr: (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12)[];
}>
*/
const foo12 = foo0
    .x(1).x(2).x(3).x(4).x(5).x(6).x(7).x(8).x(9).x(10).x(11).x(12);
/*
Expected:

+ Similar to foo12
OR
+ Type instantiation is excessively deep and possibly infinite.ts(2589)

Actual:
const foo13: Foo<{
    arr: any[];
}>
*/
const foo13 = foo12.x(13);

Playground

Using this,

type AppendToArray<ArrT extends any[], T> = (
    (
        ArrT[number]|
        T
    )[]
);
interface Data {
    arr : number[]
};
type AppendToFoo<C extends Data, N extends number> = (
    Foo<{arr:AppendToArray<C["arr"], N>}>
);

class Foo<DataT extends Data> {
    arr! : DataT["arr"];
    //Using `this`
    x<N extends number> (n:N) : AppendToFoo<this, N> {
        return null!;
    }
}
declare const foo0 : Foo<{arr:0[]}>;
/*
const foo9: Foo<{
    arr: (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9)[];
}>
*/
const foo9 = foo0
    .x(1).x(2).x(3).x(4).x(5).x(6).x(7).x(8).x(9);
/*
Expected:

+ Similar to foo9
OR
+ Type instantiation is excessively deep and possibly infinite.ts(2589)

Actual:
const foo10: Foo<{
    arr: any[];
}>
*/
const foo10 = foo9.x(10);

Playground

Expected behavior:

Both examples should resolve correctly or trigger, Type instantiation is excessively deep and possibly infinite.ts(2589)

See code for more details.

Actual behavior:

Both examples resolve to any without errors.
Both examples seem to have a different limit before this happens?

See code for more details.

Playground Link:

Using DataT, the limit seems to be 12.

Playground

Using this, the limit seems to be 9.

Playground

Related Issues:

I have no clue how to search for this.


I came across this weird bug by accident, actually.

I was working on a personal project and this would be the 4th, or 6th time I've rewritten the project. I've noticed that in all the rewrites, there's this particular method on generic class where the max number of times I could chain it was always 20.

The 21st call would always trigger the max depth error.

I was sick of it and decided to investigate possible ways to work around this limit.

I decided to write a simple repro before messing with it. (The above code snippets).
However, the simplified repro behaved very weirdly, and would not trigger the error.

It boggles me how TS can resolve crazy types like this,

image

but will choke on simple types like the above snippets.


It's also super weird because my super complex examples have a limit of 20 calls. And these super simple examples have a super low limit.

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Aug 5, 2019

Huh.

For the DataT example, I just did,
foo13.arr and finally got the max-depth error.
That's interesting.

const foo13 = foo12.x(13);
//Type instantiation is excessively deep and possibly infinite.
foo13.arr

Playground


Same this for the this example,

const foo10 = foo9.x(10);
//Type instantiation is excessively deep and possibly infinite.
foo10.arr

Playground

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Aug 5, 2019

Also, I have an API question. (So, it should be allowed on this issue tracker, right?).

Here is an implementation that uses a different generic type parameter,

type AppendToArray<ArrT extends any[], T> = (
    (
        ArrT[number]|
        T
    )[]
);

class Foo<ArrT extends number[]> {
    arr! : ArrT;
    x<N extends number> (n:N) : Foo<AppendToArray<ArrT, N>> {
        return null!;
    }
}
declare const foo0 : Foo<0[]>;

const foo10 = foo0.x(1).x(2).x(3).x(4).x(5).x(6).x(7).x(8).x(9).x(10);
const foo20 = foo10.x(11).x(12).x(13).x(14).x(15).x(16).x(17).x(18).x(19).x(20);
const foo30 = foo20.x(21).x(22).x(23).x(24).x(25).x(26).x(27).x(28).x(29).x(30);
const foo40 = foo30.x(31).x(32).x(33).x(34).x(35).x(36).x(37).x(38).x(39).x(40);
const foo50 = foo40.x(41).x(42).x(43).x(44).x(45).x(46).x(47).x(48).x(49).x(50);
/*
const foo60: Foo<(0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14
| 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30
| 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ... 17 more ... | 60)[]>
*/
const foo60 = foo50.x(51).x(52).x(53).x(54).x(55).x(56).x(57).x(58).x(59).x(60);

Playground

As you can see, I can go up to 60 and it's still fine!


So, my question is, why does TS have such a high limit when the type parameter is just number[] but have such a low limit when the type parameter is a non-array object ({arr:number[]})?

On my project, my real use case, I have to use the non-array object because this object will have a lot of properties and I want them all to be generic. Using one type param per property would be unmanageable.

And I'm still surprised my real use case supports 20 consecutive method calls when this number[] thing does not.

@weswigham
Copy link
Member

weswigham commented Aug 5, 2019

FYI, in your screenshot above, the type is actually super "simple" from a construction perspective, the printback is just garbage because the object is anonymous but recursive. If you make the object that you're indexing into (assuming it's one of the immediately indexed object patterns) an interface, ie:

interface AppendWorker<ArrT extends any[], T> {
    0: ...;
    1: ...;
}
type Append<ArrT extends any[], T> = AppendWorker<ArrT, T>[...];

you should get a much more sane printback!

Edit: Oh, lol, your example is just real deep. Yeah, that's just like 80+ levels of arrays within arrays, lol.

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Aug 5, 2019

I intentionally wrote that nonsensical piece of code in the above screenshot to see if it would crash TS. I was very surprised when it did not!

I guess it wasn't as "crazy" as I thought.

@weswigham
Copy link
Member

So re: the example in the OP - if I had to guess, the error is triggering, but there's no active node to put the error on, since it's triggering during some kind of signature resolution that reset the current node, rather than inside an expression typecheck (or the result was cached in such a situation and then reused for the expression). That's my guess, anyway. It probably indicates a codepath we neglect to set a currentNode on.

@weswigham weswigham added the Bug A bug in TypeScript label Aug 5, 2019
@AnyhowStep
Copy link
Contributor Author

Seems like even going up to 230 is fine if I use number[] instead of {arr:number[]}.

Playground

I guess TS just doesn't like object type params?

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Aug 5, 2019

I just changed that "crazy" example to use an object type param instead of an array type param,

type Append<ArrT extends any[], T> = (
    (
        ArrT[number]|
        T
    )[]
);

class X<ArrT extends {arr:any[]}> {
    t! : ArrT["arr"];
    x () : X<{arr:Append<ArrT["arr"], ArrT["arr"]>}> {
        return null!;
    }
}
declare const x : X<{arr:[]}>;
/*
const okay: X<{
    arr: ([] | [][] | ([] | [][])[] | ([] | [][] | ([] | [][])[])[] 
    | ([] | [][] | ([] | [][])[] | ([] | [][] | ([] | [][])[])[])[] 
    | ([] | [][] | ([] | [][])[] | ([] | [][] | ([] | [][])[])[] 
    | ([] | [][] | ([] | [][])[] | ([] | [][] | ([] | [][])[])[])[])[] 
    | ([] | [][] | ([] | [][])[] | ([] | [][] | ([] | [][])[])[] 
    | ([] | [][] | ([] | [][])[] | ([] | [][] | ([] | [][])[])[])[] 
    | ([] | [][] | ([] | [][])[] | ([] | ... 1 more ... | ([] | [][])[])[] 
    | ([] | ... 2 more ... | ([] | ... 1 more ... | ([] | [][])[])[])[])[])[] 
    | ... 8 more ... | ([] | ... 13 more ... | ([] | ... 12 more ... 
    | ([] | ... 11 more ... | ([] | ... 10 more ... 
    | ([] | ... 9 more ... | ([] | ... 8 more ... | ([] | ... 7 more ... 
    | ([] | ... 6 more ... | ([] | ... 5 more ... | ([] | ... 4 more ... 
    | ([] | ... 3 more ... | ([] | ... 2 more ... | ([] | ... 1 more ... 
    | ([] | [][])[])[])[])[])[])[])[])[])[])[])[])[])[])[])[];
}>
*/
const okay = x.x().x().x().x().x().x().x().x().x().x().x().x().x().x().x().x();
/*
const givesAny: X<{
    arr: any[];
}>
*/
const givesAny = okay.x();

Playground

It seems like the limit is way lower after that change.

16 calls is the max.

And, like the original examples, it doesn't give the max depth error.


Can I file a separate bug report about the max depth limit of object type params vs array type params?
(I know, arrays are technically objects)

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Aug 5, 2019

I have another example that is similar to the original examples but this one correctly shows the max depth error,

type AppendToArray<ArrT extends any[], T> = (
    (
        ArrT[number]|
        T
    )[]
);
interface Data {
    arr : number[]
};
type AppendToFoo<C extends Data, N extends number> = (
    WhyDoesThisWorkFoo<{arr:AppendToArray<C["arr"], N>}>
);

/**
 * Changed to use `number[]`, instead of an object.
 */
class Foo<ArrT extends number[]> {
    arr! : ArrT;
    //Using `this`
    x<N extends number> (n:N) : AppendToFoo<this, N> {
        return null!;
    }
}
/*
    This  uses an object, instead of `number[]`
    I don't know why this works.
*/
interface WhyDoesThisWorkFoo<DataT extends Data> extends Foo<DataT["arr"]> {}

declare const foo0 : WhyDoesThisWorkFoo<{arr:0[]}>;
/*
const foo9: WhyDoesThisWorkFoo<{
    arr: (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9)[];
}>
*/
const foo9 = foo0
    .x(1).x(2).x(3).x(4).x(5).x(6).x(7).x(8).x(9);
/*
This works now,
const foo10: WhyDoesThisWorkFoo<{
    arr: (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10)[];
}>
*/
const foo10 = foo9.x(10);
/*
This works now,
const foo20: WhyDoesThisWorkFoo<{
    arr: (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 
    | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20)[];
}>
*/
const foo20 = foo10.x(11).x(12).x(13).x(14).x(15).x(16).x(17).x(18).x(19).x(20);
const foo24 = foo20.x(21).x(22).x(23).x(24);
/*
I finally get,
Type instantiation is excessively deep and possibly infinite.
*/
const foo25 = foo24.x(25);
//            ~~~~~~~~~~~~ Type instantiation is excessively deep and possibly infinite.

Playground

And I don't know why this has a higher limit than the original examples, even though it is more complex.

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Aug 5, 2019

@weswigham

Here's an even more mind boggling example,

type AppendToArray<ArrT extends any[], T> = (
    (
        ArrT[number]|
        T
    )[]
);
interface Data {
    arr : number[]
};
/*
    Uses object instead of `number[]`
*/
interface InterfaceFoo<DataT extends Data> {
    arr : DataT["arr"];
}
type AppendToFoo<C extends Data, N extends number> = (
    ClassFoo<{arr:AppendToArray<C["arr"], N>}>
);

/*
    Uses object instead of `number[]`
*/
class ClassFoo<DataT extends Data> implements InterfaceFoo<DataT> {
    arr! : DataT["arr"];
    //Using `this`
    x<N extends number> (n:N) : AppendToFoo<this, N> {
        return null!;
    }
}

declare const foo0 : ClassFoo<{arr:0[]}>;
/*
const foo9: ClassFoo<{
    arr: (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9)[];
}>
*/
const foo9 = foo0
    .x(1).x(2).x(3).x(4).x(5).x(6).x(7).x(8).x(9);
/*
Expected:
Max depth error OR resolve correctly

Actual:
const foo10: ClassFoo<{
    arr: any[];
}>
*/
const foo10 = foo9.x(10);
/*
Expected:
Max depth error OR resolve correctly

Actual:
const foo20: ClassFoo<{
    arr: any[];
}>
*/
const foo20 = foo10.x(11).x(12).x(13).x(14).x(15).x(16).x(17).x(18).x(19).x(20);
/*
Expected:
Max depth error OR resolve correctly

Actual:
const foo24: ClassFoo<{
    arr: any[];
}>
*/
const foo24 = foo20.x(21).x(22).x(23).x(24);
/*
I finally get,
Type instantiation is excessively deep and possibly infinite.
*/
const foo25 = foo24.x(25);
//            ~~~~~~~~~~~~ Type instantiation is excessively deep and possibly infinite.

Playground

It is similar to the WhyDoesThisWorkFoo<> example because you get the max depth error at .x(25).

However it is also similar to the original example because .x(10) onward gives you any.

What's mind boggling is that you do not get any errors and still get any from .x(10) till .x(24).

I have no idea why.


Expected: .x(10) onward should give error (or resolve correctly).

Actual:

  • .x(10) onward gives any without error.
  • .x(25) onward gives max depth error. (Just like the WhyDoesThisWorkFoo<> example)

@fatcerberus From 10 to 24, it's like the compiler is in a superposition of having given up and not given up on resolving the type.

I dub it Schrödinger's type resolution

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Aug 5, 2019

Also, I'd like to point out that that the above examples show there's something weird with what TS thinks is "too deep".

Like, there's not just a bug with errors not appearing.
There's a bug where it thinks simple stuff is too complex (and thinks complex stuff is simple).

Like maybe the instantiationDepth tracking code isn't quite right. But I don't know enough about the code base to say that for sure =/

It's just a gut feeling, after having played around with the type system a lot.

@weswigham
Copy link
Member

Absolutely - the depth limit is as low as it is simply because if it were much higher you'd get a stack overflow because instantiation is written as a set of mutually recursive functions. That's why I opened #32611 - all we really want to limit is really complex types (instantiation is effectively type execution, given conditionals, and we do not want to let types execute forever ❤️ ), not deep types, and using a trampoline allows us to remove the stack limit-based constraint.

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Aug 5, 2019

In my quest for finding a workaround for the original examples, I have found it.

It relies on two types. AppendToFoo and AppendToFooImpl.

AppendToFoo takes an object type param.
AppendToFooImpl takes one type param per property of the object type param.

This means AppendToFooImpl can have a lot of type params.

I do not know why it works.
I just know it works.

@weswigham
Do you have any clues why this workaround works? =(
(And if I can rely on this workaround not breaking)


However, I have also found another weird bug.
image

Playground

So, with the Playground code, it'll resolve foo230 okay sometimes.
But it'll also get RangeError: Maximum call stack size exceeded most of the time.

And it'll switch between resolving successfully and Maximum call stack size exceeded.

Then, after leaving the tab idle, it'll just.... crash the tab.

VS code and tsc have no issues with foo230

@AnyhowStep
Copy link
Contributor Author

I'm still experimenting with the workaround on VS code and tsc.

I cranked it up to foo2000 and emitted every single type from foo10 to foo1010 to foo1500 all the way to foo2000.

It type-checked and emitted.
image

As you can see from the above screenshot, the emitted .d.ts file is so huge that VS code gave up on displaying the output and gave me ellipsis instead!

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Aug 5, 2019

[Edit]

In the following tests, I only export the very last const fooXxx variable. Everything else is not exported.


I went all the way to foo5228 with, --noEmit and no tsconfig.json configured. (So, default settings?)
Going up to 5229 crashes it.

I'd post the Playground but the URL is too long.

So, here's the first part of the repro,

/*
The workaround consists of `AppendToFoo` and `AppendToFooImpl`.

`AppendToFoo` will take an object type param.
`AppendToFooImpl` will take one type param per property of the object type param.
*/

type AppendToArray<ArrT extends any[], T> = (
    (
        ArrT[number]|
        T
    )[]
);
interface Data {
    arr : number[],
    otherThing : string,
};
interface InterfaceFoo<DataT extends Data=Data> {
    arr : DataT["arr"];
    otherThing : DataT["otherThing"];
}

/**
 * You **must not** pass `Data` to this type.
 * Instead, you must pass one type param per `Data` property.
 */
type AppendToFooImpl<N extends number, ArrT extends number[], OtherThingT extends string> = (
    ClassFoo<{
        arr:AppendToArray<ArrT, N>,
        otherThing : (
            OtherThingT extends "haha" ?
            "hehe" :
            OtherThingT extends "hehe" ?
            "hihi" :
            "haha"
        ),
    }>
);
/**
 * You may also use `Data` instead of `InterfaceFoo`
 */
type AppendToFoo<InterfaceT extends InterfaceFoo, N extends number> = (
    AppendToFooImpl<N, InterfaceT["arr"], InterfaceT["otherThing"]>
);

class ClassFoo<DataT extends Data> implements InterfaceFoo<DataT> {
    arr! : DataT["arr"];
    otherThing! : DataT["otherThing"];
    x<N extends number> (_n:N) : AppendToFoo<this, N> {
        return null!;
    }
}

declare const foo0 : ClassFoo<{arr:0[], otherThing:"Hello"}>;

Then, use this to generate as many cases as necessary,

function chain (nStart, nEnd) {
	const arr = [];
	for (let i=nStart; i<=nEnd; ++i) {
		arr.push(`.x(${i})`);
	}
	return `const foo${nEnd} = foo${nStart-1}` + arr.join("") + ";";
}
function chainMore () {
	arr = [];
	for (let i=1; i<10000; i+=10) {
		arr.push((i+10<10000 ? "" : "export ") + chain(i, i+9));
	}
	return arr.join("\n");
}
chainMore()

[Edit]

Modified the code above to generate an export on the very last line.


With my configured tsconfig.json and --noEmit, I can go up to 5397.
5398 kills it.

Files:                                    106
Lines:                                  45252
Nodes:                                 215229
Identifiers:                            74368
Symbols:                               264257
Types:                                  54691
Memory used:                         1465041K
Assignability cache size:                  26
Identity cache size:                        0
Subtype cache size:                         0
I/O Read time:                          0.01s
Parse time:                             0.36s
Program time:                           0.45s
Bind time:                              0.20s
Check time:                            11.84s
transformTime time:                     0.32s
Source Map time:                        0.00s
commentTime time:                       0.32s
printTime time:                         1.82s
Emit time:                              1.82s
Total time:                            14.32s
Project:                  /src/max-call-stack

With emit enabled and tsconfig.json configured, I can go up to 5306.
5307 kills it.

Files:                                    106
Lines:                                  45243
Nodes:                                 214729
Identifiers:                            74259
Symbols:                               335551
Types:                                  82102
Memory used:                         1459346K
Assignability cache size:                6415
Identity cache size:                      118
Subtype cache size:                         0
I/O Read time:                          0.01s
Parse time:                             0.38s
Program time:                           0.47s
Bind time:                              0.20s
Check time:                            12.59s
transformTime time:                     0.77s
Source Map time:                        0.56s
commentTime time:                       0.04s
printTime time:                         1.99s
Emit time:                              1.99s
I/O Write time:                         0.33s
Total time:                            15.25s
Project:                  /src/max-call-stack

image

And my configured tsconfig.json

{
    "compilerOptions": {
        "sourceMap": true,
        "declarationMap": true,
        "declaration": true,
        "diagnostics": true,
        "extendedDiagnostics": true,
        "composite": true,

        "experimentalDecorators": false,

        "allowJs": false,

        "allowUnreachableCode": false,
        "allowUnusedLabels": false,
        "alwaysStrict": true,

        "newLine": "lf",

        "noEmitOnError": true,
        "noImplicitAny": true,
        "noImplicitReturns": true,
        "noImplicitThis": true,
        "noImplicitUseStrict": false,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "noFallthroughCasesInSwitch": true,
        "noErrorTruncation": true,

        "strict": true,

        "module": "commonjs",
        "moduleResolution": "node",
        "target": "esnext",
        "lib": ["esnext"],
        "jsx": "react",
        "jsxFactory": "React.createElement"
    },
    "compileOnSave": false
}

So, it seems like tsc will emit fine with 5306 but tsserver gives me any without an error.

image


It seems like tsserver's limit is 580 (approximate).

image

At 590, tsserver gives me any.


TL;DR

tsconfig.json? emit? max limit
N N 5228
Y N 5397
Y Y 5306
Y tsserver 580 (approximate)

Also, I ran these builds a bunch of times and it seems like the max limit is consistent. If it crashes with 5307, it'll always crash with 5307. If it works with 5306, it'll always work with 5306.

I did modify my tsc.js to output Project: /src/max-call-stack, though. I am unsure if that will interfere with my tests.

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Aug 5, 2019

I just tested this workaround on Version 3.6.0-dev.20190804.

Using tsconfig.json with --noEmit, 5306 type checked just fine.
It did not run into the 5M type instantiations error.

With emit enabled 5306 tsc crashed (which means it has a lower limit than 3.5.1).

[Edit]
5304 emitted just fine. 5305 crashed.

Did I accidentally stumble upon a trampoline-like workaround? o.0

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Aug 5, 2019

Seems like the max limit goes down as version numbers go up. Makes sense. The compiler gets more complex. I'm surprised it doesn't go down much, though.

Version Max limit
3.3.4 5310+ (5320 crashes it, didn't get exact number)
3.5.1 5306
3.6.0-dev.20190804 5304

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Aug 15, 2019

Just leaving a note behind for anyone who may follow after me (future me?),

There is another way to sidestep TS' max depth limit and apparent "type amnesia".
But it is highly situational and probably won't apply most of the time.

For this hack/optimization to work,

  • The input type must possibly contain the exact output type.
    • If the exact output type is found among the input type, reuse it
    • Otherwise, create a new type

The workaround is here,
https://github.com/AnyhowStep/tsql/blob/00c27384d5e114acb7119a626a4bd4224e05d200/src/expr-library/operator/make-chainable-operator.ts#L80

We use a bunch of conditional types to check if we can "re-use" an existing type from the type params.


The most important part of the workaround is here,
https://github.com/AnyhowStep/tsql/blob/00c27384d5e114acb7119a626a4bd4224e05d200/src/expr-library/operator/make-chainable-operator.ts#L60

If we have found a re-usable type, we re-use it and do not "create" a new type.

If you "create" a new type, TS doesn't realize that the newly "created" type is the same as an existing type, and this will incur a max-depth penalty.

If you re-use an existing type, you sidestep the issue.


And the tests showing 100+ nested function calls,

Before this hack, I could only get 37 nested calls. The 38th would error.

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Aug 15, 2019

Okay, better version of the hack.
It is soooo much cleaner now,
https://github.com/AnyhowStep/tsql/blob/02b6982bd04dab3e420ac0efb8ccb8be31f86227/src/expr-library/operator/make-chainable-operator.ts#L66

The type that makes it possible is this,
https://github.com/AnyhowStep/tsql/blob/02b6982bd04dab3e420ac0efb8ccb8be31f86227/src/type-util/is-strict-same-type.ts#L22.

The TryReuseExistingType<A1, A2> utility type searches for an exact occurrence of A2 inside of A1. If it is found, it reuses it. Otherwise, it uses A2.

...
I just realized the type won't work right if A2 is a union type...

Well, whatever. Future me problem.


And an extra test,
https://github.com/AnyhowStep/tsql/blob/02b6982bd04dab3e420ac0efb8ccb8be31f86227/test/compile-time/input/expr-library/operator/logical/and/long-chain-3.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants