We TypeScript developers are a lucky bunch. While some languages (Python, JavaScript) are released annually, every three years (C++) or even less, we get four new versions of TypeScript every year. TypeScript 5.6 was released on September 9th, 2024. Let's take a look.
New Features
Disallowed Nullish and Truthy Checks
TypeScript will now alert you to certain conditionals that are always true or false:
|
Because {}
is truthy, the right-hand side of the ||
is dead code. It should either be removed or investigated, since it might indicate a logic error.
If your project is large and has been around for a while, this check is likely to turn up some strange-looking code. For example, I got a "this expression is always truthy" error on code that looked like this:
|
What's that || {}
doing there? Running git blame revealed the story. The code originally looked like this:
|
Then a subsequent change added prop: value
to the object and didn't remove the fallback. In this case, it's fine to remove the || {}
since using object spread on a null
/undefined
value is OK.
This new check is the single best reason to update to TS 5.6. I haven't seen a single false positive, and I've found lots of strange-looking code. This matches the TypeScript team's findings.
Iterator Helper Methods
In addition to finding new errors in your code, new TypeScript releases continue the ongoing process of implementing all stage 3 ECMAScript features.
TypeScript 5.6 now supports Iterator Helper methods like map
and take
. If you've ever used Python's itertools
package, this will be familiar. The appeal of iterators is that you can apply a series of operations to an array, for example, without constructing all the intermediate arrays. This reduces memory usage and should improve cache efficiency and performance.
Because these are JavaScript runtime methods, you'll need to use a runtime that supports them. At the moment that's Node.js 22 (which should enter long-term support in October) and around 67% of browsers. Unless you can guarantee support in your environment, you may want to wait on these for a bit.
Strict Builtin Iterator Checks (and --strictBuiltinIteratorReturn)
TypeScript's any
type is dangerous: not only does it disable type checking, it can also silently spread through your program. Chapter 5 of Effective TypeScript is all about taming the any
type.
Perhaps the scariest source of any
types is type declaration files (.d.ts
). If you call a function and it's declared to return any
, then any
is what you get, even if the word "any" never appears in your source code. JSON.parse
is a famous example of this:
|
(Matt Pocock's ts-reset fixes this and a few other well known issues.)
One subtle source of any
came from direct use of an iterator's .next()
method:
|
The type in TS 5.6 makes a lot of sense! If the Set were empty, oneLetter
would be undefined
. Otherwise it would be a string
. (You can also check the done
property to narrow the type.) While directly working with an iterator is rare (you should typically use for-of
loops or the new iterator helpers), this is a welcome improvement because it eliminates a surprising source of any
types.
So the real question is… why was this an any
type in older versions of TypeScript? To understand why, the TypeScript blog gives this example:
|
A generator function (which returns an iterator) can both yield
and return
values. When it returns a value, that goes into the value
property of the iterator's value.
TypeScript models this with two type parameters: Iterator<T, TReturn>
. Most iterators don't return a special value when they're done, so TReturn
is typically void
(the return type of a function without a return
statement).
When TypeScript first added support for iterators in 2016, they didn't distinguish T
and TReturn
. When they did split these types in 2019, they had to default TReturn
to any
to maintain backwards compatibility. The kicked the can down the road for years until this release, when they added a new flag, strictBuiltinIteratorReturn
, to fix it. This is enabled with --strict
, so you should get it right away.
A few more quick notes on this:
- The types around iterators, generators and async iterators are all pretty confusing. I hope to write a blog post about them at some point in the future.
- If you don't have
strictNullChecks
enabled, you may see some strange errors aroundvalue
having a type ofstring | void
. The fix is to enablestrictNullChecks
! - This was a surprising source of
any
types that could spread in your code. To limit the damage from these sorts ofany
s, consider using typescript-eslint'sno-unsafe-assignment
, York Yao's type-coverage tool, or my brand-new Any X-Ray Vision VS Code extension.
The --noUncheckedSideEffectImports Option
I first noticed this issue when I was working on the second edition of Effective TypeScript. I claimed that this would be an error:
|
… but it wasn't! This is a pretty strange TypeScript behavior. For these "side-effect imports," where you don't import any symbols, TypeScript will try to resolve the path to the module. If it can, it will type check the file that you import. But if it can't, it will just ignore the import entirely.
Now you can change this behavior with noUncheckedSideEffectImports
. If you use CSS imports, you're likely to get tons of errors when you first enable this, one for every import. The solution that the release notes suggest is to add this line to a .d.ts
file:
|
But this feels a bit too lenient. It will catch a typo if you get the extension wrong (.cs
instead of .css
). But it won't check that you're importing a file that exists. I experimented with listing all my CSS files in a .d.ts
file:
|
But this didn't seem to work at all. Relative imports of these files still produced type errors. So I think this feature still needs some work to be useful.
Region-Prioritized Diagnostics in Editors
Like most compilers, TypeScript is self-hosting: tsc
is written in TypeScript. This is a good idea because it's a form of dogfooding. The idea is that, since the TS team works in TypeScript every day, they'll be acutely aware of all the same issues that face other TypeScript developers.
Sometimes, though, this can have strange consequences. I suspect that most developers who contribute to TypeScript had a chuckle when they saw Region-Prioritized Diagnostics in Editors in the TS 5.6 release notes. The idea is that, for very large TypeScript files, the editor can focus on just the part that you're editing, rather than checking the whole file.
Sounds like a nice performance win. So why did I find this funny? It's because it's so clearly targeted at just one file, TypeScript's 50,000+ line checker.ts
. It's incredible to me that the TS team implemented this feature rather than breaking up that file, but there you go!
New Errors
Whenever a new version of TypeScript comes out, I like to run it over all my projects and the code samples in Effective TypeScript using literate-ts to look for new errors. There were a few of them, including some surprises.
Several errors came from the new checks I discussed earlier in this post, "this expression is always truthy" and .next()
calls having stricter types. These were all true positives: they flagged code that was suspicious.
There were also two types of errors that came as surprises.
One was a change in circularity detection for a code sample in Effective TypeScript, in Item 33: Push Null Values to the Perimeter of Your Types:
|
In the first edition of Effective TypeScript, the same snippet avoided destructuring assignment in the else
clause due to the circularity error:
|
I filed an issue about this in 2019 and was excited to see that it was fixed with TS 5.4, just in time for the book release. Unfortunately, the fix got reverted and we're back to the circularity error. So I'll need to update the book.
I also ran into an issue where the inferred type parameters for a generic function call changed. It boiled down to something like this:
|
I used every-ts to bisect this to #57909. This PR changed how type inference worked between covariant and contravariant parameters. If you see a surprising type change like this after updating to TS 5.6, this change might be the reason.
After reading some comments, this all seems pretty murky. There's often no clearly correct inference, just tradeoffs. Given that, I'm a bit surprised that TypeScript changed the existing behavior. Be on the lookout for this one!
Performance changes
New TypeScript releases have the potential to speed up or slow down compile times, but I was unable to measure any significant changes with this release.
Conclusions
While TS 5.6 isn't quite the blockbuster that TS 5.5 was, the new "this expression is always truthy" checks and the more precise iterator types make it a worthwhile upgrade.
It's sometimes said that software dependencies obey a "reverse triangle inequality:" it's easier to go from v1→v2→v3 than it is to go from v1→v3 directly. The idea is that you can fix a smaller set of issues at a time. There's not much reason to hold off on adopting TypeScript 5.6. Doing so now will make upgrading to 5.7 easier in a few months.
Speaking of which, keep an eye on that release! I'm hoping that it will include the proposed enforceReadonly
flag.