This PR implements support for null- and undefined-aware types and strict null checking that can be enabled with a new--strictNullChecks
compiler switch. For context on this topic, see discussion in#185.
Null- and undefined-aware types
TypeScript has two special types, Null and Undefined, that have the valuesnull
andundefined
respectively. Previously it was not possible to explicitly name these types, butnull
andundefined
may now be used as type names regardless of type checking mode.
The type checker previously considerednull
andundefined
assignable to anything. Effectively,null
andundefined
were valid values ofevery type and it wasn't possible to specifically exclude them (and therefore not possible to detect erroneous use of them). This changes in the new strict null checking mode.
In strict null checking mode, thenull
andundefined
values arenot in the domain of every type and are only assignable to themselves andany
(the one exception being thatundefined
is also assignable tovoid
). So, whereasT
andT | undefined
are considered synonymous in regular type checking mode (becauseundefined
is considered a subtype of anyT
), they are different types in strict type checking mode, and onlyT | undefined
permitsundefined
values. The same is true for the relationship ofT
toT | null
.
// Compiled with --strictNullChecksletx:number;lety:number|undefined;letz:number|null|undefined;x=1;// Oky=1;// Okz=1;// Okx=undefined;// Errory=undefined;// Okz=undefined;// Okx=null;// Errory=null;// Errorz=null;// Okx=y;// Errorx=z;// Errory=x;// Oky=z;// Errorz=x;// Okz=y;// Ok
Assigned-before-use checking
In strict null checking mode the compiler requires every reference to a local variable of a type that doesn't includeundefined
to be preceded by an assignment to that variable in every possible preceding code path.
For example:
// Compiled with --strictNullChecksletx:number;lety:number|null;letz:number|undefined;x;// Error, reference not preceded by assignmenty;// Error, reference not preceded by assignmentz;// Okx=1;y=null;x;// Oky;// Ok
The compiler checks that variables are definitely assigned by performingcontrol flow based type analysis. For further details on this topic, see#8010.
Optional parameters and properties
Optional parameters and properties automatically haveundefined
added to their types, even when their type annotations don't specifically includeundefined
. For example, the following two types are identical:
// Compiled with --strictNullCheckstypeT1=(x?:number)=>string;// x has type number | undefinedtypeT2=(x?:number|undefined)=>string;// x has type number | undefined
Non-null and non-undefined type guards
A property access or a function call produces a compile-time error if the object or function is of a type that includesnull
orundefined
. However, type guards are extended to support non-null and non-undefined checks. For example:
// Compiled with --strictNullChecksdeclarefunctionf(x:number):string;letx:number|null|undefined;if(x){f(x);// Ok, type of x is number here}else{f(x);// Error, type of x is number? here}leta=x!=null ?f(x) :"";// Type of a is stringletb=x&&f(x);// Type of b is string?
Non-null and non-undefined type guards may use the==
,!=
,===
, or!==
operator to compare tonull
orundefined
, as inx != null
orx === undefined
. The effects on subject variable types accurately reflect JavaScript semantics (e.g. double-equals operators check for both values no matter which one is specified whereas triple-equals only checks for the specified value).
Dotted names in type guards
Type guards previously only supported checking local variables and parameters. Type guards now support checking "dotted names" consisting of a variable or parameter name followed one or more property accesses. For example:
interfaceOptions{location?:{x?:number;y?:number;};}functionfoo(options?:Options){if(options&&options.location&&options.location.x){constx=options.location.x;// Type of x is number}}
Type guards for dotted names also work with user defined type guard functions and thetypeof
andinstanceof
operators and do not depend on the--strictNullChecks
compiler option.
A type guard for a dotted name has no effect following an assignment to any part of the dotted name. For example, a type guard forx.y.z
will have no effect following an assignment tox
,x.y
, orx.y.z
.
Expression operators
Expression operators permit operand types to includenull
and/orundefined
but always produce values of non-null and non-undefined types.
// Compiled with --strictNullChecksfunctionsum(a:number|null,b:number|null){returna+b;// Produces value of type number}
The&&
operator addsnull
and/orundefined
to the type of the right operand depending on which are present in the type of the left operand, and the||
operator removes bothnull
andundefined
from the type of the left operand in the resulting union type.
// Compiled with --strictNullChecksinterfaceEntity{name:string;}letx:Entity|null;lets=x&&x.name;// s is of type string | nulllety=x||{name:"test"};// y is of type Entity
Type widening
Thenull
andundefined
types arenot widened toany
in strict null checking mode.
letz=null;// Type of z is null
In regular type checking mode the inferred type ofz
isany
because of widening, but in strict null checking mode the inferred type ofz
isnull
(and therefore, absent a type annotation,null
is the only possible value forz
).
Non-null assertion operator
A new!
postfix expression operator may be used to assert that its operand is non-null and non-undefined in contexts where the type checker is unable to conclude that fact. Specifically, the operationx!
produces a value of the type ofx
withnull
andundefined
excluded. Similar to type assertions of the forms<T>x
andx as T
, the!
non-null assertion operator is simply removed in the emitted JavaScript code.
// Compiled with --strictNullChecksfunctionvalidateEntity(e:Entity?){// Throw exception if e is null or invalid entity}functionprocessEntity(e:Entity?){validateEntity(e);lets=e!.name;// Assert that e is non-null and access name}
Compatibility
The new features are designed such that they can be used in both strict null checking mode and regular type checking mode. In particular, thenull
andundefined
types are automatically erased from union types in regular type checking mode (because they are subtypes of all other types), and the!
non-null assertion expression operator is permitted but has no effect in regular type checking mode. Thus, declaration files that are updated to use null- and undefined-aware types can still be used in regular type checking mode for backwards compatiblity.
In practical terms, strict null checking mode requires that all files in a compilation are null- and undefined-aware.We are contemplating a pragma of some form that can be added to declaration files such that the compiler can verify that all referenced declaration files are null- and undefined-aware in a compilation that uses strict null checking.
Uh oh!
There was an error while loading.Please reload this page.
This PR implements support for null- and undefined-aware types and strict null checking that can be enabled with a new
--strictNullChecks
compiler switch. For context on this topic, see discussion in#185.Null- and undefined-aware types
TypeScript has two special types, Null and Undefined, that have the values
null
andundefined
respectively. Previously it was not possible to explicitly name these types, butnull
andundefined
may now be used as type names regardless of type checking mode.The type checker previously considered
null
andundefined
assignable to anything. Effectively,null
andundefined
were valid values ofevery type and it wasn't possible to specifically exclude them (and therefore not possible to detect erroneous use of them). This changes in the new strict null checking mode.In strict null checking mode, the
null
andundefined
values arenot in the domain of every type and are only assignable to themselves andany
(the one exception being thatundefined
is also assignable tovoid
). So, whereasT
andT | undefined
are considered synonymous in regular type checking mode (becauseundefined
is considered a subtype of anyT
), they are different types in strict type checking mode, and onlyT | undefined
permitsundefined
values. The same is true for the relationship ofT
toT | null
.Assigned-before-use checking
In strict null checking mode the compiler requires every reference to a local variable of a type that doesn't include
undefined
to be preceded by an assignment to that variable in every possible preceding code path.For example:
The compiler checks that variables are definitely assigned by performingcontrol flow based type analysis. For further details on this topic, see#8010.
Optional parameters and properties
Optional parameters and properties automatically have
undefined
added to their types, even when their type annotations don't specifically includeundefined
. For example, the following two types are identical:Non-null and non-undefined type guards
A property access or a function call produces a compile-time error if the object or function is of a type that includes
null
orundefined
. However, type guards are extended to support non-null and non-undefined checks. For example:Non-null and non-undefined type guards may use the
==
,!=
,===
, or!==
operator to compare tonull
orundefined
, as inx != null
orx === undefined
. The effects on subject variable types accurately reflect JavaScript semantics (e.g. double-equals operators check for both values no matter which one is specified whereas triple-equals only checks for the specified value).Dotted names in type guards
Type guards previously only supported checking local variables and parameters. Type guards now support checking "dotted names" consisting of a variable or parameter name followed one or more property accesses. For example:
Type guards for dotted names also work with user defined type guard functions and the
typeof
andinstanceof
operators and do not depend on the--strictNullChecks
compiler option.A type guard for a dotted name has no effect following an assignment to any part of the dotted name. For example, a type guard for
x.y.z
will have no effect following an assignment tox
,x.y
, orx.y.z
.Expression operators
Expression operators permit operand types to include
null
and/orundefined
but always produce values of non-null and non-undefined types.The
&&
operator addsnull
and/orundefined
to the type of the right operand depending on which are present in the type of the left operand, and the||
operator removes bothnull
andundefined
from the type of the left operand in the resulting union type.Type widening
The
null
andundefined
types arenot widened toany
in strict null checking mode.In regular type checking mode the inferred type of
z
isany
because of widening, but in strict null checking mode the inferred type ofz
isnull
(and therefore, absent a type annotation,null
is the only possible value forz
).Non-null assertion operator
A new
!
postfix expression operator may be used to assert that its operand is non-null and non-undefined in contexts where the type checker is unable to conclude that fact. Specifically, the operationx!
produces a value of the type ofx
withnull
andundefined
excluded. Similar to type assertions of the forms<T>x
andx as T
, the!
non-null assertion operator is simply removed in the emitted JavaScript code.Compatibility
The new features are designed such that they can be used in both strict null checking mode and regular type checking mode. In particular, the
null
andundefined
types are automatically erased from union types in regular type checking mode (because they are subtypes of all other types), and the!
non-null assertion expression operator is permitted but has no effect in regular type checking mode. Thus, declaration files that are updated to use null- and undefined-aware types can still be used in regular type checking mode for backwards compatiblity.In practical terms, strict null checking mode requires that all files in a compilation are null- and undefined-aware.
We are contemplating a pragma of some form that can be added to declaration files such that the compiler can verify that all referenced declaration files are null- and undefined-aware in a compilation that uses strict null checking.