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...