What's Happening?
In TypeScript 4.8, for projects with strictNullChecks enabled, TypeScript will now correctly issue an error when an unconstrained type parameter is used in a position where null or undefined are not legal values.
The simplest example is:
// Unconstrained type parameter T...
function foo<T>(x: T) {
// ... passed to bar's 'value' parameter
bar(x);
}
// Accepts any non-null non-undefined value
function bar(value: {}) {
Object.keys(value); // <- throws on null/undefined
}
bar(undefined); // Correct error: this throws
foo(undefined); // Not an error (4.7), bad. Correctly errors in 4.8
Why?
As demonstrated above, code like this has a potential bug - the values null and undefined can be indirectly passed through these unconstrained type parameters to code that is not supposed to observe those values.
Other Manifestations
This behavior will also be visible in type positions. One example would be:
interface Foo<T> {
x: Bar<T>;
}
interface Bar<T extends { }> { }
How do I fix this?
The exact fix you should do depends on your code, but potential options are:
Add a Constraint
In the first example, we can add a constraint:
function foo<T extends {}>(x: T) {
// ++++++++++
This will ensure that foo is not called with a value of null or undefined. The best constraint to write depends on the intent of the function:
- To accept only objects (i.e. strings, numbers, and booleans are not allowed), write
T extends object
- To accept any value that is not
null or undefined, write T extends { }
- If your lint rules say you shouldn't write those types, disable that lint rule
Note that adding a constraint to foo might cause new errors to appear in callers of foo. You may have to repeat this process a few times to fix all call chains in your program.
Test for Null/Undefined
If your intent was still to allow null / undefined values to be passed to foo but didn't intend to crash bar, you can check the value before calling it:
function foo<T>(x: T) {
if (x != null) {
bar(x); // OK
}
}
Non-null assertion operator / postfix !
If you know for other reasons that a value can't be null or undefined, you can use ! after an expression to indicate this:
function foo<T>(x: T) {
/* Magic 8 Ball tells me that x is actually never null/undefined */
bar(x!); // OK
}
Fixes in .d.ts files
This check can cause errors to appear even in declaration files:
interface Foo<T> {
// Error because potential instantiation of Foo<undefined>
// illegally produces an undefined property value at Foo.x.v
x: Bar<T>;
}
interface Bar<T extends {}> {
v: T;
}
In this case, the correct fix is either to add a constraint either matching or subsuming the other type's constraint (interface Foo<T extends {}>), or use intersection:
interface Foo<T> {
x: Bar<T & {}>;
}
interface Bar<T extends {}> {
v: T;
}
declare const m: Foo<undefined>;
m.x; // <- of type 'never'
What to Expect
In addition to fixing all impacts of this on DefinitelyTyped, we're also running an extended test run on top TypeScript projects on GitHub to look for instances of this problem, and will be sending PRs to fix these problems where possible. All potential fixes here are fully backward-compatible with TypeScript 4.7, so you can expect to be able to safely merge them.
What's Happening?
In TypeScript 4.8, for projects with
strictNullChecksenabled, TypeScript will now correctly issue an error when an unconstrained type parameter is used in a position wherenullorundefinedare not legal values.The simplest example is:
Why?
As demonstrated above, code like this has a potential bug - the values
nullandundefinedcan be indirectly passed through these unconstrained type parameters to code that is not supposed to observe those values.Other Manifestations
This behavior will also be visible in type positions. One example would be:
How do I fix this?
The exact fix you should do depends on your code, but potential options are:
Add a Constraint
In the first example, we can add a constraint:
This will ensure that
foois not called with a value ofnullorundefined. The best constraint to write depends on the intent of the function:T extends objectnullorundefined, writeT extends { }Note that adding a constraint to
foomight cause new errors to appear in callers offoo. You may have to repeat this process a few times to fix all call chains in your program.Test for Null/Undefined
If your intent was still to allow
null/undefinedvalues to be passed tofoobut didn't intend to crashbar, you can check the value before calling it:Non-null assertion operator / postfix
!If you know for other reasons that a value can't be null or undefined, you can use
!after an expression to indicate this:Fixes in .d.ts files
This check can cause errors to appear even in declaration files:
In this case, the correct fix is either to add a constraint either matching or subsuming the other type's constraint (
interface Foo<T extends {}>), or use intersection:What to Expect
In addition to fixing all impacts of this on DefinitelyTyped, we're also running an extended test run on top TypeScript projects on GitHub to look for instances of this problem, and will be sending PRs to fix these problems where possible. All potential fixes here are fully backward-compatible with TypeScript 4.7, so you can expect to be able to safely merge them.