Description
TypeScript Version: >= 3.7.5 (reproduceable in @latest and @next)
Search Terms: slow compilation typecheck types typecount
After refactoring the model of our project to rely more heavily in classes we found a very significant increase in the typecheck time. Using the --diagnostics
flag we noticed that our project seems to be producing over 2 million types (which takes around 37s to check). Half of those types came from our model definition, although that sounded like way too much, since we only have around 30 classes with little to none methods each.
Weirdest part is that the issue seems to mainly be cause by two particular method definitions. Removing these methods reduces the produced types from +900K to around 52K (around 1/6 of the original time). I'm not sure if we are doing something we are not supposed to in these methods or there is something else going on, but I would very much appreciate your insights.
I tried to purge our codebase so I could show you the smallest demo possible. You can find the trimmed example here.
The impact of the issue is, of course, less impressive, but commenting the copy
method reduces the type count from 521K to arun 24K:
Code
abstract class $Node<S extends Stage> {
...
// DELETE ME
copy(delta: Partial<Payload<this>>): this {
return new (this.constructor as any)({ ...this, ...delta })
}
transform<R extends Stage = S>(tx: (node: Node<R>) => Node<R>): Node<R> {
const applyTransform = (value: any): any => {
if (typeof value === 'function') return value
if (Array.isArray(value)) return value.map(applyTransform)
// COMMENT THIS LINE SO THE CODE COMPILES AGAIN
if (isNode<S>(value)) return value.copy(mapObject(applyTransform, tx(value as any)))
if (value instanceof Object) return mapObject(applyTransform, value)
return value
}
return applyTransform(this)
}
}
Output WITH copy
method
> tsc --noEmit --diagnostics
Files: 96
Lines: 32232
Nodes: 119346
Identifiers: 43635
Symbols: 48073
Types: 521712
Memory used: 260840K
I/O read: 0.01s
I/O write: 0.00s
Parse time: 0.50s
Bind time: 0.26s
Check time: 5.96s
Emit time: 0.00s
Total time: 6.72s
Output WITHOUT copy
method
> tsc --noEmit --diagnostics
Files: 96
Lines: 32228
Nodes: 119301
Identifiers: 43620
Symbols: 44377
Types: 24409
Memory used: 90363K
I/O read: 0.01s
I/O write: 0.00s
Parse time: 0.41s
Bind time: 0.21s
Check time: 0.98s
Emit time: 0.00s
Total time: 1.60s
tsconfig.json
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"outDir": "dist/temp",
"lib": [
"es2020"
],
"declaration": true,
"strict": true,
"removeComments": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"experimentalDecorators": true
},
"files": [
"src/model_demo.ts"
],
"exclude": [
"node_modules/**/*"
]
}
Despite my best efforts the issue still requires a couple dozens of subclasses to clearly manifest so please excuse the lengthy example.
Expected behavior: A less time consuming compilation
Actual behavior: Lots of types cause lots of check delay
Playground Link: Here