Recording and replaying events in a type-checker

kaleidawave1 pts0 comments

Recording and replaying events

Recording and replaying events

Published on Wednesday 17th June 2026

One of the biggest experiments in the Ezno type checker is the tracking of events inside functions.

I first started experimenting with the idea in early 2022 and gave a very brief introduction in the announcement blog post. Apart from a few brief mentions here, here and some hints elsewhere, I have not totally introduced the feature.

Between 2022 and 2024 the feature expanded scope, gained new uses and improved. However, it remains unfinished with incomplete functionality. Despite that I thought I would showcase what has been learnt and achieved so far.

As said this is an experimental addition ! Do not try it on your 50k LOC TypeScript codebases tomorrow.

What is a type-checker and what is Ezno?

While the term "type-checker" is quite narrow, actual exhibitors are capable of a wide range of interactions.

The collection and analysis of types can be used for the following aspects

๐ŸŸฅ Correctness

๐ŸŸจ Warnings (or linting)

๐ŸŸฆ Information

๐ŸŸง Optimisations

Where

๐ŸŸฅ Correctness relates to problems where the semantics of values mean that the program is invalid

๐ŸŸจ Warnings are similar problems but are cases where the program still completes/runs, although probably not in the way expected

๐ŸŸฆ Information relates to property autocompletion in a code editor or when the documentation for a library can tell you what a function returns (and more such as linking between types).

๐ŸŸง Finally, types can help find places where less can be done leading to a more optimised program

My description for "Ezno" is: a correct and efficient TypeScript type-checker and compiler with additional experiments . Fundamentally it is a compiler with a notable type-checker module. While it follows the baseline of TypeScript, there are some places in both the type-checker and compiler where there are additional features.

This post covers one such additional feature. Through a few small examples, we will see where and how events solve complex problems in a type-checker and provide benefit to each aspect mentioned above.

To keep this blog post short, it will not cover the any of the implementation or the entirety of the features

Variable assignment

Starting simple...

Jumping in at the shallow end, here is a simple example of code.

let score: number = 2;

if (score === 3) {<br>eatCake()<br>let score: number = 2;

if (score === 3) {<br>eatCake()

Here we have some code and we see that the equality operation will always be false as the singleton types 2 and 3 are disjoint.

It follows that eatCake will never be invoked , a property that is not desirable for this program.

We want to catch and provide a compile-time diagnostic about this problem as part of the warning ๐ŸŸจ aspect that our type-checker will implement.

A fix?

We want the reference to score to resolve to 2 in our condition. We could ignore the score: number annotation and instead have score: 2. But that would break the following assignment

let score: number /* although treat as 2 */ = 2;<br>score = 5 // Type 5 is not assignable to type 2<br>let score: number /* although treat as 2 */ = 2;<br>score = 5 // Type 5 is not assignable to type 2

Eventually I arrived on the idea that a variable has two types: a read type and a write type .

Our context table looks like the following at the score = 5 line.

Variable name<br>Write type (constraint)<br>Read type (value)

score<br>number

When we assign to the variable (as an l-value), we lookup (and check) its type using the constraint table. However, when we reference it (as an r-value), we answer with the type found in the value table.

let score: number = 2;<br>score = 5; // 5 satisfies number :). we now set the value of x to 5<br>score satisfies 5;<br>let score: number = 2;<br>score = 5; // 5 satisfies number :). we now set the value of x to 5<br>score satisfies 5;

But sometimes assignment to variables, are not obvious...

let score: number = 2;

increaseScore();

// score is 3 after `increaseScore` is called

if (score === 3) {<br>eatCake()

function increaseScore() {<br>score = 3; //<br>let score: number = 2;

increaseScore();

// score is 3 after `increaseScore` is called

if (score === 3) {<br>eatCake()

function increaseScore() {<br>score = 3; //

Free variable assignments

In JavaScript a function can reference and assign to anything from a higher lexical environment. Here we see that after the increaseScore call our score value gets assigned to 3.

This sort of variable is called (and referred to in the checker) as a free variable.

Unfortunately our table reacts to assignment-like expressions and not function invocations. This mutation is sort of hidden in the source. The value in the top scope changes semantically rather than syntactically. There are no type annotations or such that describe that score is updated.

What next?

While we could scrap the whole variable two-value idea... we should not get defeatist this early and lose the benefits of the more accurate...

score type number checker value variable

Related Articles