Ben's Engineering Blog

Ezno 2025

It has been over 6 months since the last post and after a break I have a little bit of time again to work on my type checker and compiler project!

Firstly some random achievements from last year (2024) that I have not got around to sharing yet.

Not-so-recent achievements

Adding narrowing

Narrowing is the modification of the "backing" type of parameters and variables based on runtime expressions. It is now implemented in the checker.

narrowing from playground

This marks the last big feature of the TypeScript type system to be implemented. Remaining blockers in the type checker are either related to synthesis across modules, async and generator code and the finishing of my other crazy type system experiments.

If there is interest I may do a dedicated post on the process of implementing type narrowing.

Support for Proxy

My arch-nemesis in the JavaScript language, Proxy is a special object that can have custom behaviour when used. Basic partial application and some subtyping checks (but not all) are implemented in the checker.

proxy in playgound

Additionally thanks to a PR, support for RegExps also works. At some point I write a blog summarising how custom objects such as Proxy and RegExp work in the checker.

Tree-shaking

While the parser and type-checker make up most of the code in the project, there are other tools that use the generated AST and type checking information. Thanks to behavior in the checker, it does not become too difficult to add tree-shaking to the output. Because of support for the Proxy above, the following becomes possible.

command output

While this is based on results from the type checker, it is not the actual types that enable this behaviour.

New lexer design

A parser turns strings into a tree-like data structure representing the program. Typically this is done by first "tokenising" the input string and then passing that off to a recursive-descent-parser to construct the data structure.

Previously I got a bit carried away with an over-engineered design that produced tokens in one thread and read them in a second. This had lots of problems with edge case syntax where the lexer required information from the higher level parsing.

In an on-going effort to take learnings to make the project simpler, I removed this system in terms of a more basic implementation wrapper for a string reader.

...and often simplification leads to improved performance.

change in diagnostics

this has been merged but is not yet released under the parser crate at the time of writing

More on the parser

On the topic of the parser I have been experimenting with more features including

...and partial syntax, which enables the checker to still function while editing the source.

partial syntax at work

In the future there should be variable name suggestions at the cursor

Again if there is interest I may do a dedicated blog post on the whole of the parser

Immediately next up

Here are some things I have started but have not completed yet

Updating the specification

"The specification" is a markdown document that contains a bunch of tests for type-checking behavior.

This document is invaluable for describing the state of the project as avoiding regressions with changes to the codebase.

However, I am not satisfied with the current state. So I have started to work on updating it. The updates are as follows

Again I may write a blog post on my approach to testing the checker and some ideas I have for this tool!

Add a flag to disable the events and partial application system

The events system is a unique part of the type checker and has major consequences for checking behavior.

While it has solved some of the hard problems I have wanted to solve with this checker, it is unfortuantly quite complex and current falls short to be effective in real world projects.

Because the system does not currently work in many scenarios, it is holding back the other type checker features from being used.

So last year I started on moving the system behing a flag. With some more testing a opt in --basic flag would disable events on the project and the checker would use a simpler variable and object system that is more on par with TypeScript.

I have been wanting to do a blog post on the state of this partial implemented partial application feature but I think for now it would better as a talk. If you run a meetup and interested in this then get in contact!

Finish the inference addition

Last year I had spent some time on adding a constraint inference implementation. A feature that the Hegel type checker had.

I have a few things working but there is still more to do. It would also be good to work with some real world untyped code in parralel with specific cases.

I ended up binning the original implementation (from the announcement) as it did a lot of mutation that breaks thing in conditional branches and elsewhere. The newer implementation is now to collect requirements at the end of scope, rather than modifying the type of parameters as the parameter is being used.

Add test262 checking

For JavaScript there exists a test suite of JavaScript code. It is mainly designed for engines that evaluate the code. But we can use the over 50 000 tests cases for the parser in this project.

On my WIP branch, the parser passes 91.8% of these. I should note that of the 4111 failing tests, 2395 are "parsed when should have failed". They could be duplicate parameter names, regular expressions or others. The first two are implemented at the type checker level, but could be up the chain under a flag. Now that this statistic is tracked it should only go upwards!

While the current tests only run parsing, the type checker could also be run on the files. This could uncover any crashes or places where it might not terminate. Some of these tests could also be used to test the partial application system!

An API for the type system

I think the most developed part of this project is the subtyping and disjoint code. You can see an example of how it is used here.

As that functionality is pretty much ready, it could be exposed to other project through an API.

let mut space = Space::new();

assert!(space.is_assignable_to("number", "number | string"));
assert!(!space.is_assignable_to("number | string", "number"));
let mut space = Space::new();

assert!(space.is_assignable_to("number", "number | string"));
assert!(!space.is_assignable_to("number | string", "number"));

If you are interested in using subtype logic, you can leave feedback for developing an API for it here.

Future

Other ideas that are on backburner

Continue on improving performance and architecture

While the tool is fast enough, it is a personal endeavour to learn new ways to make it even faster.

I have been working on a few things

More complete-ness: async, Promises, reading .d.ts files etc and focus more on examples

Now that narrowing has been finished, the last parts of checking are async code, generators and the whole module system.

I have been pushing back some of these later features for a while. With a mode that disables the events system, it could become alot easier to get basic support working for these final items.

Releasing syntax ideas (under flags)

I have had some ideas for syntax extensions. They have been sitting in my private experiments folder but maybe it is worth posting them, less as a feature to go out and use but as some interesting ideas of what can be done with types!

Other information

Soon I will be even more available, I am looking for preferable short-term/internship opportunities!

  • I have experience with: Rust, parsers/compilers, things that involve "types" and web (frontend/backend)
  • Random other current interests: natural language processing, internal tooling, databases and video.

And again if there is a place that would be good to host a talk on the progress on the events system (or any other Rust and TypeScript stuff)! You can contact me here.


Thanks for reading the update! and all going well, more features soon!