Description
TypeScript Version: 3.5.0-dev.20190523
Search Terms: method, generic, interface, slow check time, this
Code
Scroll to the bottom to see the repository link and reproduction steps.
For now, I'll post a rough example of what I am experiencing,
I first had something like this,
type Foo<U, Blah> = /* some complicated type */;
type Bar<V, Blah> = /* some complicated type */;
interface Interface1<T> { /* some complicated type */ }
type Baz<B> = /* some complicated type */;
interface Interface0<T> extends Interface1<T> {
foo<U> (u : U) : (Baz<Foo<U, this>>);
//A bunch of other methods.
}
The above compiles in 2s. Nothing suspicious.
Files: 130
Lines: 38454
Nodes: 171047
Identifiers: 56900
Symbols: 54146
Types: 18772
Memory used: 111991K
I/O read: 0.01s
I/O write: 0.03s
Parse time: 0.50s
Bind time: 0.27s
Check time: 1.35s
Emit time: 0.48s
Total time: 2.59s
Then, I added one more method,
type Foo<U, Blah> = /* some complicated type */;
type Bar<V, Blah> = /* some complicated type */;
interface Interface1<T> { /* some complicated type */ }
type Baz<B> = /* some complicated type */;
interface Interface0<T> extends Interface1<T> {
foo<U> (u : U) : (Baz<Foo<U, this>>);
//The new method
bar<V> (v : V) : (Baz<Bar<V, this>>);
//A bunch of other methods.
}
And then it took 102s!
Files: 130
Lines: 38452
Nodes: 171976
Identifiers: 57291
Symbols: 120444
Types: 61118
Memory used: 174232K
I/O read: 0.01s
I/O write: 0.03s
Parse time: 0.52s
Bind time: 0.34s
Check time: 101.11s
Emit time: 0.53s
Total time: 102.51s
This was very suspicious. I looked at the output .d.ts
file but there was nothing out of place.
In fact, emit only took 0.53s
I assumed this new method must be the culprit.
However, I decided to comment out everything but the new method,
type Foo<U, Blah> = /* some complicated type */;
type Bar<V, Blah> = /* some complicated type */;
interface Interface1<T> { /* some complicated type */ }
type Baz<B> = /* some complicated type */;
interface Interface0<T> extends Interface1<T> {
//commented out
//foo<U> (u : U) : (Baz<Foo<U, this>>);
//The new method
bar<V> (v : V) : (Baz<Bar<V, this>>);
/*
//commented out
//A bunch of other methods.
*/
}
This time, it only took 3s.
Files: 130
Lines: 38452
Nodes: 171950
Identifiers: 57280
Symbols: 88571
Types: 38360
Memory used: 126678K
I/O read: 0.01s
I/O write: 0.24s
Parse time: 0.47s
Bind time: 0.26s
Check time: 1.79s
Emit time: 0.71s
Total time: 3.23s
So, the problem wasn't this new method, it seemed.
I was so confused. This didn't make sense.
Then, I got to thinking, "What if it was the number of methods?"
Like, just add one more arbitrary method and see what happens.
Copy+paste foo<>()
and rename it to foo2
. Change nothing else,
interface Interface0<T> extends Interface1<T> {
//No longer commented
foo<U> (u : U) : (Baz<Foo<U, this>>);
foo2<U> (u : U) : (Baz<Foo<U, this>>);
//Notice bar<V>() is now deleted
//No longer commented
//A bunch of other methods.
}
The above now compiles in 47+s. What?
Files: 130
Lines: 38461
Nodes: 171976
Identifiers: 57291
Symbols: 118614
Types: 57314
Memory used: 139677K
I/O read: 0.01s
I/O write: 0.04s
Parse time: 0.51s
Bind time: 0.29s
Check time: 45.83s
Emit time: 0.56s
Total time: 47.20s
I looked at the generated .d.ts file and I didn't see any explosion of types.
It looked exactly the way I predicted it should and only took up 29 lines.
So, without foo2<>()
, it takes 2s. With it, it takes 47s.
And foo2<>()
is exactly the same as foo<>()
Copy+paste foo<>()
(again) and rename it to foo3
. Change nothing else,
interface Interface0<T> extends Interface1<T> {
foo<U> (u : U) : (Baz<Foo<U, this>>);
foo2<U> (u : U) : (Baz<Foo<U, this>>);
foo3<U> (u : U) : (Baz<Foo<U, this>>);
//A bunch of other methods.
}
I started this build at 22:20, 2019 May 27.
Well, it's been checking for 40 minutes and hasn't finished yet.
Expected behavior:
- Adding copies of
foo<>()
without any changes shouldn't increase check time. - If
bar<>()
alone compiles in 3s, andx<>(), y<>(), z<>()
compile in 2s, compiling all of these methods should not take 102s.
Actual behavior:
- Check time is crazy long.
- It's still checking as of this writing.
- My laptop, a ThinkPad P52, is making scary exhaust fan noises.
- My laptop is the hottest it has ever been, I think the CPU might melt.
Github Link:
https://github.com/AnyhowStep/type-mapping/tree/d09851dc437aa25b702a27fa7b6eb5060dd476c5/src
- Clone the repository
npm install
npm run build
- Grab coffee
The problem is specifically in src/fluent.ts
You'll see derive2<>(), derive3<>()
. Uncommented, it'll take forever to check.
https://github.com/AnyhowStep/type-mapping/blob/d09851dc437aa25b702a27fa7b6eb5060dd476c5/src/fluent.ts#L64
You'll see rename<>()
. Uncommented, it'll take 100s to check.
But if you comment everything else but rename<>()
, it'll take 3s to check.
https://github.com/AnyhowStep/type-mapping/blob/d09851dc437aa25b702a27fa7b6eb5060dd476c5/src/fluent.ts#L86
Related Issues:
I know there are a bunch of performance related issues around here.
I've even opened a few myself in the past.
But I'm not even sure what is related to this.
As a side note, I usually implement my libraries with just a function composition API.
Then, when I want to make it more usable, I add a fluent API on top of it.
But it seems like, with TypeScript, whenever I start to build the fluent API wrapper, I get weird problems like reaching the max instantiation depth, or super-long check times.
I guess function composition > fluent?
I feel like this problem might have to do with the fact that it's a generic interface and the return type of the methods reference the this
type.
This reminds me of TREE(3)
.
- TREE(1) = 1
- TREE(2) = 3
- TREE(3) > googolplex
Except,
- foo(1) = 2
- foo(2) = 47
- foo(3) > TREE(3) (Proof is left as an exercise to the reader)