Skip to main content

TypeScript 4.4 and more readable code

ยท 5 min read

An exciting feature is shipping with TypeScript 4.4. It has the name "Control Flow Analysis of Aliased Conditions" which is quite a mouthful. This post unpacks what this feature is, and demonstrates the contribution it makes to improving the readability of code.

Updated 30th September 2021

This blog evolved to become a talk:

Indirect type narrowing via const

On June 24th 2021, an issue on the TypeScript GitHub repository with the title "Indirect type narrowing via const" was closed by Anders Hejlsberg. The issue had been open since 2016 and it was closed as it was covered by a pull request addressing control flow analysis of aliased conditional expressions and discriminants.

It's fair to say that the TypeScript community was very excited about this, both judging from reactions on the issue:

Screenshot of reactions on GitHub

And also the general delight on Twitter:

Screenshot of reactions on Twitter

What Zeh said is a great explanation of the significance of this feature:

Lack of type narrowing with consts made me repeat code, or avoid helpfully namef consts, too many times

With this feature we're going to have the possibility of more readable code, and less repetition. That's amazing!

The code we would like to write

Rather than starting with an explanation of what this new language feature is, let's instead start from the position of writing some code and seeing what's possible with TypeScript 4.4 that we couldn't tackle previously.

Here's a simple function that adds all the parameters it receives and returns the total. It's a tolerant function and will allow people to supply numbers in the form of strings as well; so it would successfully process '2' as it would 2. This is, of course, a slightly contrived example, but should be useful for demonstrating the new feature.

function add(...thingsToAdd: (string | number)[]): number {
let total = 0;
for (const thingToAdd of thingsToAdd) {
if (typeof thingToAdd === 'string') {
total += Number(thingToAdd);
} else {
total += thingToAdd;
}
}
return total;
}

console.log(add(1, '7', '3', 9));

Try it out in the TypeScript playground.

If we look at this function, whilst it works, it's not super expressive. The typeof thingToAdd === 'string' performs two purposes:

  1. It narrows the type from string | number to string
  2. It branches the logic, such that the string can be coerced into a number and added to the total.

You can infer this from reading the code. However, what if we were to re-write it to capture intent? Let's try creating a shouldCoerceToNumber constant which expresses the action we need to take:

function add(...thingsToAdd: (string | number)[]): number {
let total = 0;
for (const thingToAdd of thingsToAdd) {
const shouldCoerceToNumber = typeof thingToAdd === 'string';
if (shouldCoerceToNumber) {
total += Number(thingToAdd);
} else {
total += thingToAdd;
}
}
return total;
}

console.log(add(1, '7', '3', 9));

Try it out in the TypeScript playground.

This is valid code; however TypeScript 4.3 is choking with an error:

Screenshot of the TypeScript playground running TypeScript 4.3 and throwing an error on our new code

The error being surfaced is:

Operator '+=' cannot be applied to types 'number' and 'string | number'.(2365)

What's happening here, is TypeScript does not remember that shouldCoerceToNumber represents a type narrowing of thingToAdd from string | number to string. So the type of thingToAdd remains unchanged from string | number when we write code that depends upon it.

This has terrible consequences. It means we can't write this more expressive code that we're interested in, and would be better for maintainers of our codebase. And this is what TypeScript 4.4, with our new feature, unlocks. Let's change the playground to use TypeScript 4.4 instead:

Screenshot of the TypeScript playground running TypeScript 4.4 and working with our new code - it shows the `thingToAdd` variable has been narrowed to a `string`

Try it out in the TypeScript playground.

Delightfully, we no longer have errors now we've made the switch. And as the screenshot shows, the thingToAdd variable has been narrowed to a string. This is because Control Flow Analysis of Aliased Conditions is now in play.

So we're now writing more expressive code, and TypeScript is willing us on our way.

Read more

This feature is a tremendous addition to the TypeScript language. It should have a significant long-term positive impact on how people write code with TypeScript.

To read more, do check out the excellent TypeScript 4.4 beta release notes. There's also some other exciting feature shipping with this release as well. Thanks very much to the TypeScript team for once again improving the language, and making a real contribution to people being able to write readable code.

This post was originally published on LogRocket.