Use union types for all common classes of AST nodes
Like Node, Expression, TypeNode, and the like.
This allows us to make many more switch statements in our codebase exhaustive (a change not included in this PR, which just swaps over to unions and fixes resulting errors/lints).
This is like the Compiler-Unions perf test, except:
- Totally complete, rather than just the
compilerfolder. - Totally up-to-date (this comment will age well 😂).
Incremental rebuilds are a rebuild-accelerating thing now, so I think this is more palatable nowadays (because a fresh build does still take about 2.7x longer than main today does). Especially since using unions for all these types has tangible style and safety advantages (no more casts everywhere!). Immediately, this found some dead code (which is now gone) and some incorrect types (which are now fixed). In addition to the ergonomic niceties of using unions like this, I do think using them more like this would help us dogfood our own typesystem more in scenarios we consider more "extreme", since we have 363 different node kinds now (and usually growing), so the Node type is what we should consider a "reasonable" but large type.
There are a handful of places where I had to introduce some new casts to work around some bogus complexity errors (opened #54146 to track the issue causing it, and flagged the offending locations), but other than that this actually works pretty well without significant refactoring (other than automatically removing a ton of useless casts). The big question is really just if we, as a team, think the tradeoffs are worth it at this point or not.
Incremental rebuilds are a rebuild-accelerating thing now, so I think this is more palatable nowadays (because a fresh build does still take about 2.7x longer than main today does).
Definitely curious to do a differential profile on this one.
because a fresh build does still take about 2.7x longer than main today does
What's the impact on an editor scenario? Say, editing checker.ts. It's already pretty bad today (at least for me), and though I don't care about a fresh build being 3x as slow, I do care about editor perf.
A quick glance at a profile for this shows a bunch of suspicious new calls for getResolvedSignature which spend a lot of time in inferTypeArguments (a full second each time it happens). Seems like maybe there's one problematic function?
The 2.7x slowdown is definitely there, though.
I did a quick debug print to show which of these getResolvedSignatures took more than 500ms, and they are these expressions:
new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, /*pos*/ -1, /*end*/ -1)
createToken(value)
new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, -1, -1)
parseOptionalToken(t)
Ouch:


Also, editing in this branch unfortunately feels quite slow. Especially if you're me and have eslint running :(