Why I'm excited for Biome's type inference


A few weeks ago, Biome published their 2024 roadmap. There’s a lot of good stuff in there, but one thing has me particularly curious: type inference for implementing lint rules that rely on TypeScript type information.

There’s a lot to unpack in that single sentence. What is Biome actually trying to achieve here?

First of all, I would like to stress that they’re not trying to create a full implementation of the TypeScript compiler. And not even a full implementation of its type-checking capabilities either. For those, users are expected to continue to use the official TypeScript compiler. So what are they trying to do then?

Biome is a linter (among other things) and as such they’re competing with ESLint. One of the most popular plugins for ESLint is typescript-eslint, which is able to use TypeScript type information to implement lint rules that go beyond what ESLint could offer out of the box. I like to call those “type-informed” lint rules.

Biome has already implemented some of the lint rules from the typescript-eslint project, such as useConsistentArrayType. That rule is specific to the TypeScript syntax, but it is not a type-informed rule.

An example of a type-informed rule is await-thenable. What’s the difference? While useConsistentArrayType operates on the TypeScript syntax, await-thenable relies on actual type information to know which expressions evaluate to something thenable.

Let’s take this statement from the await-thenable example:

await createValue();

Depending on what createValue contains, the rule will either report a diagnostic, or not. Is createValue an async function, or another function returning a Promise? Then it’s fine. But if it’s not, then a diagnostic is reported. And that’s the kind of decision making that requires type information, which only type-informed rules can do.

So today, Biome is able to implement TypeScript rules that operate at a syntax level, but type-informed rules are still out of its reach. The typescript-eslint project solved this problem by talking to the real TypeScript compiler to extract type information from it. Biome could do something similar, but there’s a catch: It comes with a significant performance overhead. For some users, that overhead is worth it. But Biome is attracting users from other tools on the premise of being magnitudes faster. Going the same route as typescript-eslint wouldn’t provide a very compelling reason to switch.

So instead, Biome intends to implement a minimal subset of type inference (the part that tracks what type a given expression evaluates to) so that it is able to implement type-informed lint rules of its own. Its approach would be limited by nature, but that’s all right: Users are still expected to use the TypeScript compiler for type safety. The linter is “merely” an additional safety net, and it doesn’t need to be perfect. If it can catch some user’s mistakes, while being much faster than alternative solutions, it would be a very useful addition to its current offering.

Looking ahead

So far, I’ve only explained what Biome’s goals for 2024 are. There’s no guarantee they will be delivered in time, of course, but generally I think they make a lot of sense. But what makes me more excited than the near-term is the potential longer term impact: Could this lead to the implementation of a full TypeScript compiler in Rust?

Building a TypeScript compiler in Rust is a monumental task, but that hasn’t stopped people from trying. Most notably there are the stc and Ezno projects. Both have been at it for over a year, but neither is showing signs of offering “good enough” compatibility to replace the TypeScript compiler anytime soon. That’s not a dig at them, because I applaud their efforts. But the reason I think they face an uphill battle is because it’s very hard to convince people to switch if you only offer 70% or 80% compatibility, no matter how much faster your offering is. And getting to even that level of compatibility seems to be well over a year of effort on its own. Not to mention that even when you get to 90% compatibility, you still have 90% of the work ahead of you.

Imagine the situation where you’re building a TypeScript reimplementation from scratch, with nary an end in sight, and no users. And while you’re working on it, the TypeScript team keeps churning out new versions and your target keeps slipping away again. I don’t think anyone can fault the stc developers for giving up on their project, or the Ezno developers for not making fast enough progress.

Where Biome is different is that they intentionally don’t aim to make a reimplementation of the TypeScript compiler. For them, 70% compatibility on type inference alone would be enough to offer valuable type-informed lint rules. Assuming they get there, they will also be likely to continue to improve. And maybe more importantly, because they’re already offering value to users, the developers will be more motivated to keep up the work.

And meanwhile, almost as a side-effect of building increasingly good type inference, the team will be building the foundation on which a true type checker could be built. It may take years to get there, maybe they’ll never get there. So maybe this is all just hopeful thinking on my part. But it is indeed my hope that one day we may actually see a TypeScript reimplementation written in Rust. And I suspect that Biome may be the most promising route to get there.

Discussion

Of course there’s still plenty of challenges ahead before Biome’s type inference becomes good enough that it could power a true type checker with sufficient compatibility that users can use at a replacement. First they’ll need compatibility with .d.ts files, NPM compatibility, various module resolution modes, and plenty of non-trivial stuff. Is it feasible? Other challenges I hadn’t thought of? Or do you have suggestions that might help the Biome project?

Join the Biome Discord server to discuss, or reply to the thread on Mastodon.